| /* MPEG-PS muxer plugin for GStreamer |
| * Copyright 2008 Lin YANG <oxcsnicho@gmail.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| /* |
| * Unless otherwise indicated, Source Code is licensed under MIT license. |
| * See further explanation attached in License Statement (distributed in the file |
| * LICENSE). |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of |
| * this software and associated documentation files (the "Software"), to deal in |
| * the Software without restriction, including without limitation the rights to |
| * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is furnished to do |
| * so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in all |
| * copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| |
| #include "psmuxcommon.h" |
| #include "psmuxstream.h" |
| #include "psmux.h" |
| |
| static guint8 psmux_stream_pes_header_length (PsMuxStream * stream); |
| static void psmux_stream_write_pes_header (PsMuxStream * stream, guint8 * data); |
| static void psmux_stream_find_pts_dts_within (PsMuxStream * stream, guint bound, |
| gint64 * pts, gint64 * dts); |
| |
| /** |
| * psmux_stream_new: |
| * @pid: a PID |
| * @stream_type: the stream type |
| * |
| * Create a new stream with type = @stream_type, assign stream id accordingly |
| * |
| * Returns: a new #PsMuxStream. |
| */ |
| PsMuxStream * |
| psmux_stream_new (PsMux * mux, PsMuxStreamType stream_type) |
| { |
| PsMuxStream *stream = g_slice_new0 (PsMuxStream); |
| PsMuxStreamIdInfo *info = &(mux->id_info); |
| |
| stream->stream_type = stream_type; |
| stream->is_audio_stream = FALSE; |
| stream->is_video_stream = FALSE; |
| stream->stream_id = 0; |
| stream->max_buffer_size = 0; |
| |
| switch (stream_type) { |
| /* MPEG AUDIO */ |
| case PSMUX_ST_AUDIO_MPEG1: |
| case PSMUX_ST_AUDIO_MPEG2: |
| stream->max_buffer_size = 2484; /* ISO/IEC 13818 2.5.2.4 */ |
| case PSMUX_ST_AUDIO_AAC: |
| if (info->id_mpga > PSMUX_STREAM_ID_MPGA_MAX) |
| break; |
| stream->stream_id = info->id_mpga++; |
| stream->stream_id_ext = 0; |
| stream->is_audio_stream = TRUE; |
| break; |
| /* MPEG VIDEO */ |
| case PSMUX_ST_VIDEO_MPEG1: |
| case PSMUX_ST_VIDEO_MPEG2: |
| case PSMUX_ST_VIDEO_MPEG4: |
| case PSMUX_ST_VIDEO_H264: |
| if (info->id_mpgv > PSMUX_STREAM_ID_MPGV_MAX) |
| break; |
| stream->stream_id = info->id_mpgv++; |
| stream->stream_id_ext = 0; |
| stream->is_video_stream = TRUE; |
| break; |
| /* AC3 / A52 */ |
| case PSMUX_ST_PS_AUDIO_AC3: |
| if (info->id_ac3 > PSMUX_STREAM_ID_AC3_MAX) |
| break; |
| stream->stream_id = PSMUX_PRIVATE_STREAM_1; |
| stream->stream_id_ext = info->id_ac3++; |
| stream->is_audio_stream = TRUE; |
| /* AC3 requires data alignment */ |
| stream->pi.flags |= PSMUX_PACKET_FLAG_PES_DATA_ALIGN; |
| break; |
| /* TODO: SPU missing */ |
| #if 0 |
| case spu: |
| if (info->id_spu > PSMUX_STREAM_ID_SPU_MAX) |
| break; |
| return info->id_spu++; |
| #endif |
| /* DTS */ |
| case PSMUX_ST_PS_AUDIO_DTS: |
| if (info->id_dts > PSMUX_STREAM_ID_DTS_MAX) |
| break; |
| stream->stream_id = PSMUX_PRIVATE_STREAM_1; |
| stream->stream_id_ext = info->id_dts++; |
| stream->is_audio_stream = TRUE; |
| break; |
| /* LPCM */ |
| case PSMUX_ST_PS_AUDIO_LPCM: |
| if (info->id_lpcm > PSMUX_STREAM_ID_LPCM_MAX) |
| break; |
| stream->stream_id = PSMUX_PRIVATE_STREAM_1; |
| stream->stream_id_ext = info->id_lpcm++; |
| stream->is_audio_stream = TRUE; |
| break; |
| case PSMUX_ST_VIDEO_DIRAC: |
| if (info->id_dirac > PSMUX_STREAM_ID_DIRAC_MAX) |
| break; |
| stream->stream_id = PSMUX_EXTENDED_STREAM; |
| stream->stream_id_ext = info->id_dirac++; |
| stream->is_video_stream = TRUE; |
| break; |
| default: |
| g_critical ("Stream type 0x%0x not yet implemented", stream_type); |
| break; |
| } |
| |
| if (stream->stream_id == 0) { |
| g_critical ("Number of elementary streams of type %04x exceeds maximum", |
| stream->stream_type); |
| g_slice_free (PsMuxStream, stream); |
| return NULL; |
| } |
| |
| /* XXX: Are private streams also using stream_id_ext? */ |
| if (stream->stream_id == PSMUX_EXTENDED_STREAM) |
| stream->pi.flags |= PSMUX_PACKET_FLAG_PES_EXT_STREAMID; |
| |
| /* Are these useful at all? */ |
| if (stream->stream_id == PSMUX_PROGRAM_STREAM_MAP || |
| stream->stream_id == PSMUX_PADDING_STREAM || |
| stream->stream_id == PSMUX_PRIVATE_STREAM_2 || |
| stream->stream_id == PSMUX_ECM || |
| stream->stream_id == PSMUX_EMM || |
| stream->stream_id == PSMUX_PROGRAM_STREAM_DIRECTORY || |
| stream->stream_id == PSMUX_DSMCC_STREAM || |
| stream->stream_id == PSMUX_ITU_T_H222_1_TYPE_E) |
| stream->pi.flags &= ~PSMUX_PACKET_FLAG_PES_FULL_HEADER; |
| else |
| stream->pi.flags |= PSMUX_PACKET_FLAG_PES_FULL_HEADER; |
| |
| stream->buffers = NULL; |
| stream->bytes_avail = 0; |
| stream->cur_buffer = NULL; |
| stream->cur_buffer_consumed = 0; |
| |
| stream->cur_pes_payload_size = 0; |
| |
| stream->pts = -1; |
| stream->dts = -1; |
| stream->last_pts = -1; |
| |
| /* These fields are set by gstreamer */ |
| stream->audio_sampling = 0; |
| stream->audio_channels = 0; |
| stream->audio_bitrate = 0; |
| |
| if (stream->max_buffer_size == 0) { |
| /* XXX: VLC'S VALUE. Better default? */ |
| if (stream->is_video_stream) |
| stream->max_buffer_size = 400 * 1024; |
| else if (stream->is_audio_stream) |
| stream->max_buffer_size = 4 * 1024; |
| else /* Unknown */ |
| stream->max_buffer_size = 4 * 1024; |
| } |
| |
| return stream; |
| } |
| |
| /** |
| * psmux_stream_free: |
| * @stream: a #PsMuxStream |
| * |
| * Free the resources of @stream. |
| */ |
| void |
| psmux_stream_free (PsMuxStream * stream) |
| { |
| g_return_if_fail (stream != NULL); |
| |
| if (psmux_stream_bytes_in_buffer (stream)) { |
| g_warning ("Freeing stream with data not yet processed"); |
| } |
| g_slice_free (PsMuxStream, stream); |
| } |
| |
| /* Advance the current packet stream position by len bytes. |
| * Mustn't consume more than available in the current packet */ |
| static void |
| psmux_stream_consume (PsMuxStream * stream, guint len) |
| { |
| g_assert (stream->cur_buffer != NULL); |
| g_assert (len <= stream->cur_buffer->map.size - stream->cur_buffer_consumed); |
| |
| stream->cur_buffer_consumed += len; |
| stream->bytes_avail -= len; |
| |
| if (stream->cur_buffer_consumed == 0) |
| return; |
| |
| if (stream->cur_buffer->pts != -1) |
| stream->last_pts = stream->cur_buffer->pts; |
| |
| if (stream->cur_buffer_consumed == stream->cur_buffer->map.size) { |
| /* Current packet is completed, move along */ |
| stream->buffers = g_list_delete_link (stream->buffers, stream->buffers); |
| |
| gst_buffer_unmap (stream->cur_buffer->buf, &stream->cur_buffer->map); |
| gst_buffer_unref (stream->cur_buffer->buf); |
| g_slice_free (PsMuxStreamBuffer, stream->cur_buffer); |
| stream->cur_buffer = NULL; |
| } |
| } |
| |
| |
| /** |
| * psmux_stream_bytes_in_buffer: |
| * @stream: a #PsMuxStream |
| * |
| * Calculate how much bytes are in the buffer. |
| * |
| * Returns: The number of bytes in the buffer. |
| */ |
| gint |
| psmux_stream_bytes_in_buffer (PsMuxStream * stream) |
| { |
| g_return_val_if_fail (stream != NULL, 0); |
| |
| return stream->bytes_avail; |
| } |
| |
| /** |
| * psmux_stream_get_data: |
| * @stream: a #PsMuxStream |
| * @buf: a buffer to hold the result |
| * @len: the length of @buf |
| * |
| * Write a PES packet to @buf, up to @len bytes |
| * |
| * Returns: number of bytes having been written, 0 if error |
| */ |
| guint |
| psmux_stream_get_data (PsMuxStream * stream, guint8 * buf, guint len) |
| { |
| guint8 pes_hdr_length; |
| guint w; |
| |
| g_return_val_if_fail (stream != NULL, FALSE); |
| g_return_val_if_fail (buf != NULL, FALSE); |
| g_return_val_if_fail (len >= PSMUX_PES_MAX_HDR_LEN, FALSE); |
| |
| stream->cur_pes_payload_size = |
| MIN (psmux_stream_bytes_in_buffer (stream), len - PSMUX_PES_MAX_HDR_LEN); |
| /* Note that we cannot make a better estimation of the header length for the |
| * time being; because the header length is dependent on whether we can find a |
| * timestamp in the upcomming buffers, which in turn depends on |
| * cur_pes_payload_size, which is exactly what we want to decide. |
| */ |
| |
| psmux_stream_find_pts_dts_within (stream, stream->cur_pes_payload_size, |
| &stream->pts, &stream->dts); |
| |
| /* clear pts/dts flag */ |
| stream->pi.flags &= ~(PSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS | |
| PSMUX_PACKET_FLAG_PES_WRITE_PTS); |
| /* update pts/dts flag */ |
| if (stream->pts != -1 && stream->dts != -1) |
| stream->pi.flags |= PSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS; |
| else { |
| if (stream->pts != -1) |
| stream->pi.flags |= PSMUX_PACKET_FLAG_PES_WRITE_PTS; |
| } |
| |
| pes_hdr_length = psmux_stream_pes_header_length (stream); |
| |
| /* write pes header */ |
| GST_LOG ("Writing PES header of length %u and payload %d", |
| pes_hdr_length, stream->cur_pes_payload_size); |
| psmux_stream_write_pes_header (stream, buf); |
| |
| buf += pes_hdr_length; |
| w = stream->cur_pes_payload_size; /* number of bytes of payload to write */ |
| |
| while (w > 0) { |
| guint32 avail; |
| guint8 *cur; |
| |
| if (stream->cur_buffer == NULL) { |
| /* Start next packet */ |
| if (stream->buffers == NULL) |
| return FALSE; |
| stream->cur_buffer = (PsMuxStreamBuffer *) (stream->buffers->data); |
| stream->cur_buffer_consumed = 0; |
| } |
| |
| /* Take as much as we can from the current buffer */ |
| avail = stream->cur_buffer->map.size - stream->cur_buffer_consumed; |
| cur = stream->cur_buffer->map.data + stream->cur_buffer_consumed; |
| if (avail < w) { |
| memcpy (buf, cur, avail); |
| psmux_stream_consume (stream, avail); |
| |
| buf += avail; |
| w -= avail; |
| } else { |
| memcpy (buf, cur, w); |
| psmux_stream_consume (stream, w); |
| |
| w = 0; |
| } |
| } |
| |
| return pes_hdr_length + stream->cur_pes_payload_size; |
| } |
| |
| static guint8 |
| psmux_stream_pes_header_length (PsMuxStream * stream) |
| { |
| guint8 packet_len; |
| |
| /* Calculate the length of the header for this stream */ |
| |
| /* start_code prefix + stream_id + pes_packet_length = 6 bytes */ |
| packet_len = 6; |
| |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_FULL_HEADER) { |
| /* For a PES 'full header' we have at least 3 more bytes, |
| * and then more based on flags */ |
| packet_len += 3; |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) { |
| packet_len += 10; |
| } else if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS) { |
| packet_len += 5; |
| } |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_EXT_STREAMID) { |
| /* Need basic extension flags (1 byte), plus 2 more bytes for the |
| * length + extended stream id */ |
| packet_len += 3; |
| } |
| } |
| |
| return packet_len; |
| } |
| |
| /* Find a PTS/DTS to write into the pes header within the next bound bytes |
| * of the data */ |
| static void |
| psmux_stream_find_pts_dts_within (PsMuxStream * stream, guint bound, |
| gint64 * pts, gint64 * dts) |
| { |
| /* 1. When there are at least one buffer then output the first buffer with |
| * pts/dts |
| * 2. If the bound is too small to include even one buffer, output the pts/dts |
| * of that buffer. |
| */ |
| GList *cur; |
| |
| *pts = -1; |
| *dts = -1; |
| |
| for (cur = g_list_first (stream->buffers); cur != NULL; |
| cur = g_list_next (cur)) { |
| PsMuxStreamBuffer *curbuf = cur->data; |
| |
| /* FIXME: This isn't quite correct - if the 'bound' is within this |
| * buffer, we don't know if the timestamp is before or after the split |
| * so we shouldn't return it */ |
| if (bound <= curbuf->map.size) { |
| *pts = curbuf->pts; |
| *dts = curbuf->dts; |
| return; |
| } |
| |
| /* Have we found a buffer with pts/dts set? */ |
| if (curbuf->pts != -1 || curbuf->dts != -1) { |
| *pts = curbuf->pts; |
| *dts = curbuf->dts; |
| return; |
| } |
| |
| bound -= curbuf->map.size; |
| } |
| } |
| |
| static void |
| psmux_stream_write_pes_header (PsMuxStream * stream, guint8 * data) |
| { |
| guint16 length_to_write; |
| guint8 hdr_len = psmux_stream_pes_header_length (stream); |
| |
| /* start_code prefix + stream_id + pes_packet_length = 6 bytes */ |
| data[0] = 0x00; |
| data[1] = 0x00; |
| data[2] = 0x01; |
| data[3] = stream->stream_id; |
| data += 4; |
| |
| length_to_write = hdr_len - 6 + stream->cur_pes_payload_size; |
| |
| psmux_put16 (&data, length_to_write); |
| |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_FULL_HEADER) { |
| guint8 flags = 0; |
| |
| /* Not scrambled, original, not-copyrighted, data_alignment specified by flag */ |
| flags = 0x81; |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_DATA_ALIGN) |
| flags |= 0x04; /* Enable data_alignment_indicator */ |
| *data++ = flags; |
| |
| /* Flags */ |
| flags = 0; |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) |
| flags |= 0xC0; |
| else if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS) |
| flags |= 0x80; |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_EXT_STREAMID) |
| flags |= 0x01; /* Enable PES_extension_flag */ |
| *data++ = flags; |
| |
| /* Header length is the total pes length, |
| * minus the 9 bytes of start codes, flags + hdr_len */ |
| g_return_if_fail (hdr_len >= 9); |
| *data++ = (hdr_len - 9); |
| |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) { |
| psmux_put_ts (&data, 0x3, stream->pts); |
| psmux_put_ts (&data, 0x1, stream->dts); |
| } else if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_WRITE_PTS) { |
| psmux_put_ts (&data, 0x2, stream->pts); |
| } |
| |
| if (stream->pi.flags & PSMUX_PACKET_FLAG_PES_EXT_STREAMID) { |
| guint8 ext_len; |
| |
| flags = 0x0f; /* preceeding flags all 0 | (reserved bits) | PES_extension_flag_2 */ |
| *data++ = flags; |
| |
| ext_len = 1; /* Only writing 1 byte into the extended fields */ |
| *data++ = 0x80 | ext_len; /* marker | PES_extension_field_length */ |
| *data++ = 0x80 | stream->stream_id_ext; /* stream_id_extension_flag | extended_stream_id */ |
| } |
| } |
| } |
| |
| /** |
| * psmux_stream_add_data: |
| * @stream: a #PsMuxStream |
| * @buffer: (transfer full): buffer with data to add |
| * @pts: PTS of access unit in @data |
| * @dts: DTS of access unit in @data |
| * |
| * Submit @len bytes of @data into @stream. @pts and @dts can be set to the |
| * timestamp (against a 90Hz clock) of the first access unit in @data. A |
| * timestamp of -1 for @pts or @dts means unknown. |
| * |
| * This function takes ownership of @buffer. |
| */ |
| void |
| psmux_stream_add_data (PsMuxStream * stream, GstBuffer * buffer, |
| gint64 pts, gint64 dts, gboolean keyunit) |
| { |
| PsMuxStreamBuffer *packet; |
| |
| g_return_if_fail (stream != NULL); |
| |
| packet = g_slice_new (PsMuxStreamBuffer); |
| packet->buf = buffer; |
| |
| if (!gst_buffer_map (packet->buf, &packet->map, GST_MAP_READ)) { |
| GST_ERROR ("Failed to map buffer for reading"); |
| gst_buffer_unref (packet->buf); |
| g_slice_free (PsMuxStreamBuffer, packet); |
| return; |
| } |
| |
| packet->keyunit = keyunit; |
| packet->pts = pts; |
| packet->dts = dts; |
| |
| if (stream->bytes_avail == 0) |
| stream->last_pts = pts; |
| |
| stream->bytes_avail += packet->map.size; |
| /* FIXME: perhaps use GstQueueArray instead? */ |
| stream->buffers = g_list_append (stream->buffers, packet); |
| |
| } |
| |
| /** |
| * psmux_stream_get_es_descrs: |
| * @stream: a #PsMuxStream |
| * @buf: a buffer to hold the ES descriptor |
| * @len: the length used in @buf |
| * |
| * Write an Elementary Stream Descriptor for @stream into @buf. the number of |
| * bytes consumed in @buf will be updated in @len. |
| * |
| * @buf and @len must be at least #PSMUX_MIN_ES_DESC_LEN. |
| */ |
| void |
| psmux_stream_get_es_descrs (PsMuxStream * stream, guint8 * buf, guint16 * len) |
| { |
| guint8 *pos; |
| |
| g_return_if_fail (stream != NULL); |
| |
| if (buf == NULL) { |
| if (len != NULL) |
| *len = 0; |
| return; |
| } |
| |
| /* Based on the stream type, write out any descriptors to go in the |
| * PMT ES_info field */ |
| pos = buf; |
| |
| /* tag (registration_descriptor), length, format_identifier */ |
| switch (stream->stream_type) { |
| case PSMUX_ST_AUDIO_AAC: |
| /* FIXME */ |
| break; |
| case PSMUX_ST_VIDEO_MPEG4: |
| /* FIXME */ |
| break; |
| case PSMUX_ST_VIDEO_H264: |
| *pos++ = 0x05; |
| *pos++ = 8; |
| *pos++ = 0x48; /* 'H' */ |
| *pos++ = 0x44; /* 'D' */ |
| *pos++ = 0x4D; /* 'M' */ |
| *pos++ = 0x56; /* 'V' */ |
| /* FIXME : Not sure about this additional_identification_info */ |
| *pos++ = 0xFF; |
| *pos++ = 0x1B; |
| *pos++ = 0x44; |
| *pos++ = 0x3F; |
| break; |
| case PSMUX_ST_VIDEO_DIRAC: |
| *pos++ = 0x05; |
| *pos++ = 4; |
| *pos++ = 0x64; /* 'd' */ |
| *pos++ = 0x72; /* 'r' */ |
| *pos++ = 0x61; /* 'a' */ |
| *pos++ = 0x63; /* 'c' */ |
| break; |
| case PSMUX_ST_PS_AUDIO_AC3: |
| { |
| *pos++ = 0x05; |
| *pos++ = 4; |
| *pos++ = 0x41; /* 'A' */ |
| *pos++ = 0x43; /* 'C' */ |
| *pos++ = 0x2D; /* '-' */ |
| *pos++ = 0x33; /* '3' */ |
| |
| /* audio_stream_descriptor () | ATSC A/52-2001 Annex A |
| * |
| * descriptor_tag 8 uimsbf |
| * descriptor_length 8 uimsbf |
| * sample_rate_code 3 bslbf |
| * bsid 5 bslbf |
| * bit_rate_code 6 bslbf |
| * surround_mode 2 bslbf |
| * bsmod 3 bslbf |
| * num_channels 4 bslbf |
| * full_svc 1 bslbf |
| * langcod 8 bslbf |
| * [...] |
| */ |
| *pos++ = 0x81; |
| *pos++ = 0x04; |
| |
| /* 3 bits sample_rate_code, 5 bits hardcoded bsid (default ver 8) */ |
| switch (stream->audio_sampling) { |
| case 48000: |
| *pos++ = 0x08; |
| break; |
| case 44100: |
| *pos++ = 0x28; |
| break; |
| case 32000: |
| *pos++ = 0x48; |
| break; |
| default: |
| *pos++ = 0xE8; |
| break; /* 48, 44.1 or 32 Khz */ |
| } |
| |
| /* 1 bit bit_rate_limit, 5 bits bit_rate_code, 2 bits suround_mode */ |
| switch (stream->audio_bitrate) { |
| case 32: |
| *pos++ = 0x00 << 2; |
| break; |
| case 40: |
| *pos++ = 0x01 << 2; |
| break; |
| case 48: |
| *pos++ = 0x02 << 2; |
| break; |
| case 56: |
| *pos++ = 0x03 << 2; |
| break; |
| case 64: |
| *pos++ = 0x04 << 2; |
| break; |
| case 80: |
| *pos++ = 0x05 << 2; |
| break; |
| case 96: |
| *pos++ = 0x06 << 2; |
| break; |
| case 112: |
| *pos++ = 0x07 << 2; |
| break; |
| case 128: |
| *pos++ = 0x08 << 2; |
| break; |
| case 160: |
| *pos++ = 0x09 << 2; |
| break; |
| case 192: |
| *pos++ = 0x0A << 2; |
| break; |
| case 224: |
| *pos++ = 0x0B << 2; |
| break; |
| case 256: |
| *pos++ = 0x0C << 2; |
| break; |
| case 320: |
| *pos++ = 0x0D << 2; |
| break; |
| case 384: |
| *pos++ = 0x0E << 2; |
| break; |
| case 448: |
| *pos++ = 0x0F << 2; |
| break; |
| case 512: |
| *pos++ = 0x10 << 2; |
| break; |
| case 576: |
| *pos++ = 0x11 << 2; |
| break; |
| case 640: |
| *pos++ = 0x12 << 2; |
| break; |
| default: |
| *pos++ = 0x32 << 2; |
| break; /* 640 Kb/s upper limit */ |
| } |
| |
| /* 3 bits bsmod, 4 bits num_channels, 1 bit full_svc */ |
| switch (stream->audio_channels) { |
| case 1: |
| *pos++ = 0x01 << 1; |
| break; /* 1/0 */ |
| case 2: |
| *pos++ = 0x02 << 1; |
| break; /* 2/0 */ |
| case 3: |
| *pos++ = 0x0A << 1; |
| break; /* <= 3 */ |
| case 4: |
| *pos++ = 0x0B << 1; |
| break; /* <= 4 */ |
| case 5: |
| *pos++ = 0x0C << 1; |
| break; /* <= 5 */ |
| case 6: |
| default: |
| *pos++ = 0x0D << 1; |
| break; /* <= 6 */ |
| } |
| |
| *pos++ = 0x00; |
| |
| break; |
| } |
| case PSMUX_ST_PS_AUDIO_DTS: |
| /* FIXME */ |
| break; |
| case PSMUX_ST_PS_AUDIO_LPCM: |
| /* FIXME */ |
| break; |
| default: |
| break; |
| } |
| |
| if (len) |
| *len = (pos - buf); |
| } |
| |
| /** |
| * psmux_stream_get_pts: |
| * @stream: a #PsMuxStream |
| * |
| * Return the PTS of the last buffer that has had bytes written and |
| * which _had_ a PTS in @stream. |
| * |
| * Returns: the PTS of the last buffer in @stream. |
| */ |
| guint64 |
| psmux_stream_get_pts (PsMuxStream * stream) |
| { |
| g_return_val_if_fail (stream != NULL, -1); |
| |
| return stream->last_pts; |
| } |