diff options
author | Johns <johns98@gmx.net> | 2012-02-21 20:55:28 +0100 |
---|---|---|
committer | Johns <johns98@gmx.net> | 2012-02-21 20:55:28 +0100 |
commit | 5d8dea1b6b9e15048f425f13b349e785a494cdb3 (patch) | |
tree | ac2fc34f5cff60e63b59fa8ed5bcd5d9f6a1d7fa /softhddev.c | |
parent | 1f232db5b499169e3c354b4af4bb59053009f210 (diff) | |
download | vdr-plugin-softhddevice-5d8dea1b6b9e15048f425f13b349e785a494cdb3.tar.gz vdr-plugin-softhddevice-5d8dea1b6b9e15048f425f13b349e785a494cdb3.tar.bz2 |
New audio PES handling.
New easier and more flexible audio PES packet parser, which includes own
codec parser.
Removed av_parser use.
Reduced audio buffer time, faster channel switch.
New audio transport stream parser (not enabled as default).
Diffstat (limited to 'softhddev.c')
-rw-r--r-- | softhddev.c | 1229 |
1 files changed, 927 insertions, 302 deletions
diff --git a/softhddev.c b/softhddev.c index f33d739..3ea5e1c 100644 --- a/softhddev.c +++ b/softhddev.c @@ -71,16 +71,22 @@ static volatile char StreamFreezed; ///< stream freezed static volatile char NewAudioStream; ///< new audio stream static volatile char SkipAudio; ///< skip audio stream -static char AudioRawAc3; ///< flag raw ac3 stream static AudioDecoder *MyAudioDecoder; ///< audio decoder static enum CodecID AudioCodecID; ///< current codec id static int AudioChannelID; ///< current audio channel id -/** -** mpeg bitrate table. -** -** BitRateTable[Version][Layer][Index] -*/ +#define AUDIO_BUFFER_SIZE (512 * 1024) ///< audio PES buffer default size +static AVPacket AudioAvPkt[1]; ///< audio a/v packet + +////////////////////////////////////////////////////////////////////////////// +// Audio codec parser +////////////////////////////////////////////////////////////////////////////// + +/// +/// Mpeg bitrate table. +/// +/// BitRateTable[Version][Layer][Index] +/// static const uint16_t BitRateTable[2][4][16] = { // MPEG Version 1 {{}, @@ -96,109 +102,187 @@ static const uint16_t BitRateTable[2][4][16] = { } }; -/** -** mpeg samperate table. -*/ +/// +/// Mpeg samperate table. +/// static const uint16_t SampleRateTable[4] = { 44100, 48000, 32000, 0 }; -/** -** Find sync in audio packet. -** -** @param avpkt audio packet -** -** From: http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm -** -** AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM -** -** o a 11x frame sync -** o b 2x mpeg audio version (2.5, reserved, 2, 1) -** o c 2x layer (reserved, III, II, I) -** o e 2x BitRate index -** o f 2x SampleRate index -** o g 1x Paddding bit -** o .. doesn't care -** -** frame length: -** Layer I: -** FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4 -** Layer II & III: -** FrameLengthInBytes = 144 * BitRate / SampleRate + Padding -** -** @todo sometimes detects wrong position -*/ -static int FindAudioSync(const AVPacket * avpkt) +/// +/// Fast check for Mpeg audio. +/// +/// 4 bytes 0xFFExxxxx Mpeg audio +/// +static inline int FastMpegCheck(const uint8_t * p) { - int i; - const uint8_t *data; - - i = 0; - data = avpkt->data; - while (i < avpkt->size - 4) { - if (data[i] == 0xFF && (data[i + 1] & 0xFC) == 0xFC) { - int mpeg2; - int mpeg25; - int layer; - int bit_rate_index; - int sample_rate_index; - int padding; - int bit_rate; - int sample_rate; - int frame_size; - - mpeg2 = !(data[i + 1] & 0x08) && (data[i + 1] & 0x10); - mpeg25 = !(data[i + 1] & 0x08) && !(data[i + 1] & 0x10); - layer = 4 - ((data[i + 1] >> 1) & 0x03); - bit_rate_index = (data[i + 2] >> 4) & 0x0F; - sample_rate_index = (data[i + 2] >> 2) & 0x03; - padding = (data[i + 2] >> 1) & 0x01; - - sample_rate = SampleRateTable[sample_rate_index]; - if (!sample_rate) { // no valid sample rate try next - i++; - continue; - } - sample_rate >>= mpeg2; // mpeg 2 half rate - sample_rate >>= mpeg25; // mpeg 2.5 quarter rate - - bit_rate = BitRateTable[mpeg2 | mpeg25][layer][bit_rate_index]; - bit_rate *= 1000; - switch (layer) { - case 1: - frame_size = (12 * bit_rate) / sample_rate; - frame_size = (frame_size + padding) * 4; - break; - case 2: - case 3: - default: - frame_size = (144 * bit_rate) / sample_rate; - frame_size = frame_size + padding; - break; - } - Debug(3, - "audio: mpeg%s layer%d bitrate=%d samplerate=%d %d bytes\n", - mpeg25 ? "2.5" : mpeg2 ? "2" : "1", layer, bit_rate, - sample_rate, frame_size); - // check if after this frame a new mpeg frame starts - if (i + frame_size < avpkt->size - 3 - && data[i + frame_size] == 0xFF - && (data[i + frame_size + 1] & 0xFC) == 0xFC) { - Debug(3, "audio: mpeg1/2 found at %d\n", i); - return i; - } - // no valid frame size or no continuation, try next - } - i++; + if (p[0] != 0xFF) { // 11bit frame sync + return 0; + } + if ((p[1] & 0xE0) != 0xE0) { + return 0; + } + if ((p[1] & 0x18) == 0x08) { // version ID - 01 reserved + return 0; + } + if (!(p[1] & 0x06)) { // layer description - 00 reserved + return 0; + } + if ((p[2] & 0xF0) == 0xF0) { // bitrate index - 1111 reserved + return 0; + } + if ((p[2] & 0x0C) == 0x0C) { // sampling rate index - 11 reserved + return 0; } - return -1; + return 1; } -/** -** Possible AC3 frame sizes. -** -** from ATSC A/52 table 5.18 frame size code table. -*/ +/// +/// Check for Mpeg audio. +/// +/// 0xFFEx already checked. +/// +/// @param data incomplete PES packet +/// @param size number of bytes +/// +/// @retval <0 possible mpeg audio, but need more data +/// @retval 0 no valid mpeg audio +/// @retval >0 valid mpeg audio +/// +/// From: http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm +/// +/// AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM +/// +/// o a 11x Frame sync +/// o b 2x Mpeg audio version (2.5, reserved, 2, 1) +/// o c 2x Layer (reserved, III, II, I) +/// o e 2x BitRate index +/// o f 2x SampleRate index (4100, 48000, 32000, 0) +/// o g 1x Paddding bit +/// o .. Doesn't care +/// +/// frame length: +/// Layer I: +/// FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4 +/// Layer II & III: +/// FrameLengthInBytes = 144 * BitRate / SampleRate + Padding +/// +static int MpegCheck(const uint8_t * data, int size) +{ + int mpeg2; + int mpeg25; + int layer; + int bit_rate_index; + int sample_rate_index; + int padding; + int bit_rate; + int sample_rate; + int frame_size; + + mpeg2 = !(data[1] & 0x08) && (data[1] & 0x10); + mpeg25 = !(data[1] & 0x08) && !(data[1] & 0x10); + layer = 4 - ((data[1] >> 1) & 0x03); + bit_rate_index = (data[2] >> 4) & 0x0F; + sample_rate_index = (data[2] >> 2) & 0x03; + padding = (data[2] >> 1) & 0x01; + + sample_rate = SampleRateTable[sample_rate_index]; + if (!sample_rate) { // no valid sample rate try next + // moved into fast check + abort(); + return 0; + } + sample_rate >>= mpeg2; // mpeg 2 half rate + sample_rate >>= mpeg25; // mpeg 2.5 quarter rate + + bit_rate = BitRateTable[mpeg2 | mpeg25][layer][bit_rate_index]; + if (!bit_rate) { // no valid bit-rate try next + // FIXME: move into fast check? + return 0; + } + bit_rate *= 1000; + switch (layer) { + case 1: + frame_size = (12 * bit_rate) / sample_rate; + frame_size = (frame_size + padding) * 4; + break; + case 2: + case 3: + default: + frame_size = (144 * bit_rate) / sample_rate; + frame_size = frame_size + padding; + break; + } + if (0) { + Debug(3, + "pesdemux: mpeg%s layer%d bitrate=%d samplerate=%d %d bytes\n", + mpeg25 ? "2.5" : mpeg2 ? "2" : "1", layer, bit_rate, sample_rate, + frame_size); + } + + if (frame_size + 4 > size) { + return -frame_size - 4; + } + // check if after this frame a new mpeg frame starts + if (FastMpegCheck(data + frame_size)) { + return frame_size; + } + + return 0; +} + +/// +/// Fast check for AAC LATM audio. +/// +/// 3 bytes 0x56Exxx AAC LATM audio +/// +static inline int FastLatmCheck(const uint8_t * p) +{ + if (p[0] != 0x56) { // 11bit sync + return 0; + } + if ((p[1] & 0xE0) != 0xE0) { + return 0; + } + return 1; +} + +/// +/// Check for AAC LATM audio. +/// +/// 0x56Exxx already checked. +/// +/// @param data incomplete PES packet +/// @param size number of bytes +/// +/// @retval <0 possible AAC LATM audio, but need more data +/// @retval 0 no valid AAC LATM audio +/// @retval >0 valid AAC LATM audio +/// +static int LatmCheck(const uint8_t * data, int size) +{ + int frame_size; + + // 13 bit frame size without header + frame_size = ((data[1] & 0x1F) << 8) + data[2]; + frame_size += 3; + + if (frame_size + 2 > size) { + return -frame_size - 2; + } + // check if after this frame a new AAC LATM frame starts + if (FastLatmCheck(data + frame_size)) { + return frame_size; + } + + return 0; +} + +/// +/// Possible AC3 frame sizes. +/// +/// from ATSC A/52 table 5.18 frame size code table. +/// const uint16_t Ac3FrameSizeTable[38][3] = { {64, 69, 96}, {64, 70, 96}, {80, 87, 120}, {80, 88, 120}, {96, 104, 144}, {96, 105, 144}, {112, 121, 168}, {112, 122, 168}, @@ -212,43 +296,544 @@ const uint16_t Ac3FrameSizeTable[38][3] = { {1152, 1254, 1728}, {1280, 1393, 1920}, {1280, 1394, 1920}, }; -/** -** Find dolby sync in audio packet. -** -** @param avpkt audio packet -*/ -static int FindDolbySync(const AVPacket * avpkt) +/// +/// Fast check for AC3 audio. +/// +/// 5 bytes 0x0B77xxxxxx AC3 audio +/// +static inline int FastAc3Check(const uint8_t * p) { - int i; - const uint8_t *data; - - i = 0; - data = avpkt->data; - while (i < avpkt->size - 5) { - if (data[i] == 0x0B && data[i + 1] == 0x77) { - int fscod; - int frmsizcod; - int frame_size; - - // crc1 crc1 fscod|frmsizcod - fscod = data[i + 4] >> 6; - frmsizcod = data[i + 4] & 0x3F; - frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2; - - // check if after this frame a new ac-3 frame starts - if (i + frame_size < avpkt->size - 5 - && data[i + frame_size] == 0x0B - && data[i + frame_size + 1] == 0x77) { - Debug(3, "audio: ac-3 found at %d\n", i); - return i; - } - // no valid frame size or no continuation, try next + if (p[0] != 0x0B) { // 16bit sync + return 0; + } + if (p[1] != 0x77) { + return 0; + } + return 1; +} + +/// +/// Check for AC-3 audio. +/// +/// 0x0B77xxxxxx already checked. +/// +/// @param data incomplete PES packet +/// @param size number of bytes +/// +/// @retval <0 possible AC-3 audio, but need more data +/// @retval 0 no valid AC-3 audio +/// @retval >0 valid AC-3 audio +/// +static int Ac3Check(const uint8_t * data, int size) +{ + int fscod; + int frmsizcod; + int frame_size; + + // crc1 crc1 fscod|frmsizcod + fscod = data[4] >> 6; + frmsizcod = data[4] & 0x3F; + frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2; + + if (frame_size + 2 > size) { + return -frame_size - 2; + } + // check if after this frame a new AC-3 frame starts + if (FastAc3Check(data + frame_size)) { + return frame_size; + } + + return 0; +} + +#ifdef USE_TS_AUDIO + +////////////////////////////////////////////////////////////////////////////// +// PES Demux +////////////////////////////////////////////////////////////////////////////// + +/// +/// PES type. +/// +enum +{ + PES_PROG_STREAM_MAP = 0xBC, + PES_PRIVATE_STREAM1 = 0xBD, + PES_PADDING_STREAM = 0xBE, ///< filler, padding stream + PES_PRIVATE_STREAM2 = 0xBF, + PES_AUDIO_STREAM_S = 0xC0, + PES_AUDIO_STREAM_E = 0xDF, + PES_VIDEO_STREAM_S = 0xE0, + PES_VIDEO_STREAM_E = 0xEF, + PES_ECM_STREAM = 0xF0, + PES_EMM_STREAM = 0xF1, + PES_DSM_CC_STREAM = 0xF2, + PES_ISO13522_STREAM = 0xF3, + PES_TYPE_E_STREAM = 0xF8, ///< ITU-T rec. h.222.1 type E stream + PES_PROG_STREAM_DIR = 0xFF, +}; + +/// +/// PES parser state. +/// +enum +{ + PES_INIT, ///< unknown codec + + PES_SKIP, ///< skip packet + PES_SYNC, ///< search packet sync byte + PES_HEADER, ///< copy header + PES_START, ///< pes packet start found + PES_PAYLOAD, ///< copy payload + + PES_LPCM_HEADER, ///< copy lcpm header + PES_LPCM_PAYLOAD, ///< copy lcpm payload +}; + +#define PES_START_CODE_SIZE 6 ///< size of pes start code with length +#define PES_HEADER_SIZE 9 ///< size of pes header +#define PES_MAX_HEADER_SIZE (PES_HEADER_SIZE + 256) ///< maximal header size +#define PES_MAX_PAYLOAD (512 * 1024) ///< max pay load size + +/// +/// PES demuxer. +/// +typedef struct _pes_demux_ +{ + //int Pid; ///< packet id + //int PcrPid; ///< program clock reference pid + //int StreamType; ///< stream type + + int State; ///< parsing state + uint8_t Header[PES_MAX_HEADER_SIZE]; ///< buffer for pes header + int HeaderIndex; ///< header index + int HeaderSize; ///< size of pes header + uint8_t *Buffer; ///< payload buffer + int Index; ///< buffer index + int Skip; ///< buffer skip + int Size; ///< size of payload buffer + + uint8_t StartCode; ///< pes packet start code + + int64_t PTS; ///< presentation time stamp + int64_t DTS; ///< decode time stamp +} PesDemux; + +/// +/// Initialize a packetized elementary stream demuxer. +/// +/// @param pesdx packetized elementary stream demuxer +/// +void PesInit(PesDemux * pesdx) +{ + memset(pesdx, 0, sizeof(*pesdx)); + pesdx->Size = PES_MAX_PAYLOAD; + pesdx->Buffer = av_malloc(PES_MAX_PAYLOAD + FF_INPUT_BUFFER_PADDING_SIZE); + if (!pesdx->Buffer) { + Fatal(_("pesdemux: out of memory\n")); + } + pesdx->PTS = AV_NOPTS_VALUE; // reset + pesdx->DTS = AV_NOPTS_VALUE; +} + +/// +/// Reset packetized elementary stream demuxer. +/// +void PesReset(PesDemux * pesdx) +{ + pesdx->State = PES_INIT; + pesdx->Skip = 0; + pesdx->PTS = AV_NOPTS_VALUE; + pesdx->DTS = AV_NOPTS_VALUE; +} + +/// +/// Parse packetized elementary stream. +/// +/// @param pesdx packetized elementary stream demuxer +/// @param data payload data of transport stream +/// @param size number of payload data bytes +/// @param is_start flag, start of pes packet +/// +void PesParse(PesDemux * pesdx, const uint8_t * data, int size, int is_start) +{ + const uint8_t *p; + const uint8_t *q; + + if (is_start) { // start of pes packet + if (pesdx->Index && pesdx->Skip) { + // copy remaining bytes down + pesdx->Index -= pesdx->Skip; + memmove(pesdx->Buffer, pesdx->Buffer + pesdx->Skip, pesdx->Index); + pesdx->Skip = 0; } - i++; + pesdx->State = PES_SYNC; + pesdx->HeaderIndex = 0; + pesdx->PTS = AV_NOPTS_VALUE; // reset if not yet used + pesdx->DTS = AV_NOPTS_VALUE; } - return -1; + + p = data; + do { + int n; + + switch (pesdx->State) { + case PES_SKIP: // skip this packet + return; + + case PES_START: // at start of pes packet payload + // FIXME: need 0x80 -- 0xA0 state + if (AudioCodecID == CODEC_ID_NONE) { + if ((*p & 0xF0) == 0x80) { // AC-3 & DTS + Debug(3, "pesdemux: dvd ac-3\n"); + } else if ((*p & 0xFF) == 0xA0) { // LPCM + Debug(3, "pesdemux: dvd lpcm\n"); + pesdx->State = PES_LPCM_HEADER; + pesdx->HeaderIndex = 0; + pesdx->HeaderSize = 7; + break; + } + } + + case PES_INIT: // find start of audio packet + // FIXME: increase if needed the buffer + + // fill buffer + n = pesdx->Size - pesdx->Index; + if (n > size) { + n = size; + } + memcpy(pesdx->Buffer + pesdx->Index, p, n); + pesdx->Index += n; + p += n; + size -= n; + + q = pesdx->Buffer + pesdx->Skip; + n = pesdx->Index - pesdx->Skip; + while (n >= 5) { + int r; + unsigned codec_id; + + // 4 bytes 0xFFExxxxx Mpeg audio + // 3 bytes 0x56Exxx AAC LATM audio + // 5 bytes 0x0B77xxxxxx AC3 audio + // PCM audio can't be found + r = 0; + if (FastMpegCheck(q)) { + r = MpegCheck(q, n); + codec_id = CODEC_ID_MP2; + } + if (!r && FastAc3Check(q)) { + r = Ac3Check(q, n); + codec_id = CODEC_ID_AC3; + } + if (!r && FastLatmCheck(q)) { + r = LatmCheck(q, n); + codec_id = CODEC_ID_AAC_LATM; + } + if (r < 0) { // need more bytes + break; + } + if (r > 0) { + AVPacket avpkt[1]; + + // new codec id, close and open new + if (AudioCodecID != codec_id) { + CodecAudioClose(MyAudioDecoder); + CodecAudioOpen(MyAudioDecoder, NULL, codec_id); + AudioCodecID = codec_id; + } + av_init_packet(avpkt); + avpkt->data = (void *)q; + avpkt->size = r; + avpkt->pts = pesdx->PTS; + avpkt->dts = pesdx->DTS; + CodecAudioDecode(MyAudioDecoder, avpkt); + pesdx->PTS = AV_NOPTS_VALUE; + pesdx->DTS = AV_NOPTS_VALUE; + pesdx->Skip += r; + // FIXME: switch to decoder state + //pesdx->State = PES_MPEG_DECODE; + break; + } + // try next byte + ++pesdx->Skip; + ++q; + --n; + } + break; + + case PES_SYNC: // wait for pes sync + n = PES_START_CODE_SIZE - pesdx->HeaderIndex; + if (n > size) { + n = size; + } + memcpy(pesdx->Header + pesdx->HeaderIndex, p, n); + pesdx->HeaderIndex += n; + p += n; + size -= n; + + // have complete packet start code + if (pesdx->HeaderIndex >= PES_START_CODE_SIZE) { + unsigned code; + + // bad mpeg pes packet start code prefix 0x00001xx + if (pesdx->Header[0] || pesdx->Header[1] + || pesdx->Header[2] != 0x01) { + Debug(3, "pesdemux: bad pes packet\n"); + pesdx->State = PES_SKIP; + return; + } + code = pesdx->Header[3]; + if (code != pesdx->StartCode) { + Debug(3, "pesdemux: pes start code id %#02x\n", code); + // FIXME: need to save start code id? + pesdx->StartCode = code; + } + + pesdx->State = PES_HEADER; + pesdx->HeaderSize = PES_HEADER_SIZE; + } + break; + + case PES_HEADER: // parse PES header + n = pesdx->HeaderSize - pesdx->HeaderIndex; + if (n > size) { + n = size; + } + memcpy(pesdx->Header + pesdx->HeaderIndex, p, n); + pesdx->HeaderIndex += n; + p += n; + size -= n; + + // have header upto size bits + if (pesdx->HeaderIndex == PES_HEADER_SIZE) { + if ((pesdx->Header[6] & 0xC0) == 0x80) { + pesdx->HeaderSize += pesdx->Header[8]; + } else { + Error(_("pesdemux: mpeg1 pes packet unsupported\n")); + pesdx->State = PES_SKIP; + return; + } + // have complete header + } else if (pesdx->HeaderIndex == pesdx->HeaderSize) { + int64_t pts; + int64_t dts; + + if ((pesdx->Header[7] & 0xC0) == 0x80) { + pts = + (int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | + (data[11] & 0xFE) << 14 | data[12] << 7 | (data[13] + & 0xFE) >> 1; + Debug(4, "pesdemux: pts %#012" PRIx64 "\n", pts); + pesdx->PTS = pts; + } else if ((pesdx->Header[7] & 0xC0) == 0xC0) { + pts = + (int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | + (data[11] & 0xFE) << 14 | data[12] << 7 | (data[13] + & 0xFE) >> 1; + pesdx->PTS = pts; + dts = + (int64_t) (data[14] & 0x0E) << 29 | data[15] << 22 + | (data[16] & 0xFE) << 14 | data[17] << 7 | + (data[18] & 0xFE) >> 1; + pesdx->DTS = dts; + Debug(4, + "pesdemux: pts %#012" PRIx64 " %#012" PRIx64 "\n", + pts, dts); + } + + pesdx->State = PES_INIT; + if (pesdx->StartCode == PES_PRIVATE_STREAM1) { + // only private stream 1, has sub streams + pesdx->State = PES_START; + } + //pesdx->HeaderIndex = 0; + //pesdx->Index = 0; + } + break; + + case PES_LPCM_HEADER: // lpcm header + n = pesdx->HeaderSize - pesdx->HeaderIndex; + if (n > size) { + n = size; + } + memcpy(pesdx->Header + pesdx->HeaderIndex, p, n); + pesdx->HeaderIndex += n; + p += n; + size -= n; + + if (pesdx->HeaderIndex == pesdx->HeaderSize) { + static int samplerates[] = { 48000, 96000, 44100, 32000 }; + int samplerate; + int channels; + int bits_per_sample; + const uint8_t *q; + + if (AudioCodecID != CODEC_ID_PCM_DVD) { + + q = pesdx->Header; + Debug(3, "pesdemux: LPCM %d sr:%d bits:%d chan:%d\n", + q[0], q[5] >> 4, (((q[5] >> 6) & 0x3) + 4) * 4, + (q[5] & 0x7) + 1); + CodecAudioClose(MyAudioDecoder); + + bits_per_sample = (((q[5] >> 6) & 0x3) + 4) * 4; + if (bits_per_sample != 16) { + Error(_ + ("softhddev: LPCM %d bits per sample aren't supported\n"), + bits_per_sample); + // FIXME: handle unsupported formats. + } + samplerate = samplerates[q[5] >> 4]; + channels = (q[5] & 0x7) + 1; + AudioSetup(&samplerate, &channels, 0); + if (samplerate != samplerates[q[5] >> 4]) { + Error(_ + ("softhddev: LPCM %d sample-rate is unsupported\n"), + samplerates[q[5] >> 4]); + // FIXME: support resample + } + if (channels != (q[5] & 0x7) + 1) { + Error(_ + ("softhddev: LPCM %d channels are unsupported\n"), + (q[5] & 0x7) + 1); + // FIXME: support resample + } + //CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_PCM_DVD); + AudioCodecID = CODEC_ID_PCM_DVD; + } + pesdx->State = PES_LPCM_PAYLOAD; + pesdx->Index = 0; + pesdx->Skip = 0; + } + break; + + case PES_LPCM_PAYLOAD: // lpcm payload + // fill buffer + n = pesdx->Size - pesdx->Index; + if (n > size) { + n = size; + } + memcpy(pesdx->Buffer + pesdx->Index, p, n); + pesdx->Index += n; + p += n; + size -= n; + + if (pesdx->PTS != (int64_t) AV_NOPTS_VALUE) { + // FIXME: needs bigger buffer + AudioSetClock(pesdx->PTS); + pesdx->PTS = AV_NOPTS_VALUE; + } + swab(pesdx->Buffer, pesdx->Buffer, pesdx->Index); + AudioEnqueue(pesdx->Buffer, pesdx->Index); + pesdx->Index = 0; + break; + } + } while (size > 0); +} + +////////////////////////////////////////////////////////////////////////////// +// Transport stream demux +////////////////////////////////////////////////////////////////////////////// + + /// Transport stream packet size +#define TS_PACKET_SIZE 188 + /// Transport stream packet sync byte +#define TS_PACKET_SYNC 0x47 + +/// +/// transport stream demuxer typedef. +/// +typedef struct _ts_demux_ TsDemux; + +/// +/// transport stream demuxer structure. +/// +struct _ts_demux_ +{ + int Packets; ///< packets between PCR +}; + +static PesDemux PesDemuxAudio[1]; ///< audio demuxer + +/// +/// Transport stream demuxer. +/// +/// @param tsdx transport stream demuxer +/// @param data buffer of transport stream packets +/// @param size size of buffer +/// +/// @returns number of bytes consumed from buffer. +/// +int TsDemuxer(TsDemux * tsdx, const uint8_t * data, int size) +{ + const uint8_t *p; + + p = data; + while (size >= TS_PACKET_SIZE) { + int pid; + int payload; + + if (p[0] != TS_PACKET_SYNC) { + Error(_("tsdemux: transport stream out of sync\n")); + // FIXME: kill all buffers + return size; + } + ++tsdx->Packets; + if (p[1] & 0x80) { // error indicator + Debug(3, "tsdemux: transport error\n"); + // FIXME: kill all buffers + goto next_packet; + } + + pid = (p[1] & 0x1F) << 8 | p[2]; + Debug(4, "tsdemux: PID: %#04x%s%s\n", pid, p[1] & 0x40 ? " start" : "", + p[3] & 0x10 ? " payload" : ""); + + // skip adaptation field + switch (p[3] & 0x30) { // adaption field + case 0x00: // reserved + case 0x20: // adaptation field only + default: + goto next_packet; + case 0x10: // only payload + payload = 4; + break; + case 0x30: // skip adapation field + payload = 5 + p[4]; + // illegal length, ignore packet + if (payload >= TS_PACKET_SIZE) { + Debug(3, "tsdemux: illegal adaption field length\n"); + goto next_packet; + } + break; + } + + PesParse(PesDemuxAudio, p + payload, TS_PACKET_SIZE - payload, + p[1] & 0x40); +#if 0 + int tmp; + + // check continuity + tmp = p[3] & 0x0F; // continuity counter + if (((tsdx->CC + 1) & 0x0F) != tmp) { + Debug(3, "tsdemux: OUT OF SYNC: %d %d\n", tmp, tsdx->CC); + //TS discontinuity (received 8, expected 0) for PID + } + tsdx->CC = tmp; +#endif + + next_packet: + p += TS_PACKET_SIZE; + size -= TS_PACKET_SIZE; + } + + return p - data; } +#endif + /** ** Play audio packet. ** @@ -259,8 +844,7 @@ static int FindDolbySync(const AVPacket * avpkt) int PlayAudio(const uint8_t * data, int size, uint8_t id) { int n; - int osize; - AVPacket avpkt[1]; + const uint8_t *p; // channel switch: SetAudioChannelDevice: SetDigitalAudioDevice: @@ -270,206 +854,226 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id) if (SkipAudio || !MyAudioDecoder) { // skip audio return size; } + if (NewAudioStream) { // FIXME: does this clear the audio ringbuffer? CodecAudioClose(MyAudioDecoder); + AudioSetBufferTime(0); AudioCodecID = CODEC_ID_NONE; + AudioChannelID = -1; NewAudioStream = 0; } - // PES header 0x00 0x00 0x01 ID - // ID 0xBD 0xC0-0xCF - - if (size < 9) { - Error(_("[softhddev] invalid audio packet\n")); - return size; - } // Don't overrun audio buffers on replay if (AudioFreeBytes() < 3072 * 8 * 8) { // 8 channels 8 packets return 0; } + // PES header 0x00 0x00 0x01 ID + // ID 0xBD 0xC0-0xCF + // must be a PES start code + if (size < 9 || !data || data[0] || data[1] || data[2] != 0x01) { + Error(_("[softhddev] invalid PES audio packet\n")); + return size; + } n = data[8]; // header size - av_init_packet(avpkt); + if (size < 9 + n + 4) { // wrong size + if (size == 9 + n) { + Warning(_("[softhddev] empty audio packet\n")); + } else { + Error(_("[softhddev] invalid audio packet %d bytes\n"), size); + } + return size; + } + if (data[7] & 0x80 && n >= 5) { - avpkt->pts = + AudioAvPkt->pts = (int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 | data[12] << 7 | (data[13] & 0xFE) >> 1; - //Debug(3, "audio: pts %#012" PRIx64 "\n", avpkt->pts); + //Debug(3, "audio: pts %#012" PRIx64 "\n", AudioAvPkt->pts); } if (0) { // dts is unused if (data[7] & 0x40) { - avpkt->dts = + AudioAvPkt->dts = (int64_t) (data[14] & 0x0E) << 29 | data[15] << 22 | (data[16] & 0xFE) << 14 | data[17] << 7 | (data[18] & 0xFE) >> 1; - Debug(3, "audio: dts %#012" PRIx64 "\n", avpkt->dts); + Debug(3, "audio: dts %#012" PRIx64 "\n", AudioAvPkt->dts); } } - osize = size; - data += 9 + n; - size -= 9 + n; // skip pes header - if (size <= 0) { - Error(_("[softhddev] invalid audio packet\n")); - return osize; + p = data + 9 + n; + n = size - 9 - n; // skip pes header + if (n + AudioAvPkt->stream_index > AudioAvPkt->size) { + Fatal(_("[softhddev] audio buffer too small\n")); + AudioAvPkt->stream_index = 0; } - // Detect audio code - // MPEG-PS mp2 MPEG1, MPEG2, AC3, LPCM, AAC LATM - // only if unknown or new channel id - if (AudioCodecID == CODEC_ID_NONE || AudioChannelID != id) { + if (AudioChannelID != id) { AudioChannelID = id; - // Syncword - 0x0B77 - if (data[0] == 0x0B && data[1] == 0x77) { - if (AudioCodecID != CODEC_ID_AC3) { - Debug(3, "[softhddev]%s: AC-3 %d\n", __FUNCTION__, id); - CodecAudioClose(MyAudioDecoder); - CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_AC3); - AudioCodecID = CODEC_ID_AC3; - } - AudioRawAc3 = 1; - // Syncword - 0xFFFC - 0xFFFF - } else if (data[0] == 0xFF && (data[1] & 0xFC) == 0xFC) { - if (AudioCodecID != CODEC_ID_MP2) { - Debug(3, "[softhddev]%s: MP2 %d\n", __FUNCTION__, id); - CodecAudioClose(MyAudioDecoder); - CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_MP2); - AudioCodecID = CODEC_ID_MP2; - } - // latm header 0x56E0 11bits: 0x2B7 - } else if (data[0] == 0x56 && (data[1] & 0xE0) == 0xE0 - && (((data[1] & 0x1F) << 8) + (data[2] & 0xFF)) < size) { - if (AudioCodecID != CODEC_ID_AAC_LATM) { -#if 0 - // test harder check - printf("%d %d\n", (((data[1] & 0x1F) << 8) + (data[2] & 0xFF)), - size); - printf("%p %x %x\n", data, - data[3 + (((data[1] & 0x1F) << 8) + (data[2] & 0xFF))], - data[4 + (((data[1] & 0x1F) << 8) + (data[2] & 0xFF))]); -#endif - Debug(3, "[softhddev]%s: AAC LATM %d\n", __FUNCTION__, id); - CodecAudioClose(MyAudioDecoder); - CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_AAC_LATM); - AudioCodecID = CODEC_ID_AAC_LATM; + AudioCodecID = CODEC_ID_NONE; + } + // Private stream + LPCM ID + if ((id & 0xF0) == 0xA0) { + if (n < 7) { + Error(_("[softhddev] invalid LPCM audio packet %d bytes\n"), size); + return size; + } + if (AudioCodecID != CODEC_ID_PCM_DVD) { + static int samplerates[] = { 48000, 96000, 44100, 32000 }; + int samplerate; + int channels; + int bits_per_sample; + + Debug(3, "[softhddev]%s: LPCM %d sr:%d bits:%d chan:%d\n", + __FUNCTION__, id, p[5] >> 4, (((p[5] >> 6) & 0x3) + 4) * 4, + (p[5] & 0x7) + 1); + CodecAudioClose(MyAudioDecoder); + + bits_per_sample = (((p[5] >> 6) & 0x3) + 4) * 4; + if (bits_per_sample != 16) { + Error(_ + ("softhddev: LPCM %d bits per sample aren't supported\n"), + bits_per_sample); + // FIXME: handle unsupported formats. } - // Private stream + DVD Track ID Syncword - 0x0B77 - } else if (data[-n - 9 + 3] == 0xBD && (data[0] & 0xF0) == 0x80 - && data[4] == 0x0B && data[5] == 0x77) { - if (AudioCodecID != CODEC_ID_AC3) { - Debug(3, "[softhddev]%s: DVD Audio %d\n", __FUNCTION__, id); - CodecAudioClose(MyAudioDecoder); - CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_AC3); - AudioCodecID = CODEC_ID_AC3; + samplerate = samplerates[p[5] >> 4]; + channels = (p[5] & 0x7) + 1; + + AudioSetBufferTime(400); + AudioSetup(&samplerate, &channels, 0); + if (samplerate != samplerates[p[5] >> 4]) { + Error(_("softhddev: LPCM %d sample-rate is unsupported\n"), + samplerates[p[5] >> 4]); + // FIXME: support resample } - AudioRawAc3 = 0; - // Private stream + LPCM ID - } else if (data[-n - 9 + 3] == 0xBD && data[0] == 0xA0) { - if (AudioCodecID != CODEC_ID_PCM_DVD) { - static int samplerates[] = { 48000, 96000, 44100, 32000 }; - int samplerate; - int channels; - int bits_per_sample; - - Debug(3, "[softhddev]%s: LPCM %d sr:%d bits:%d chan:%d\n", - __FUNCTION__, id, data[5] >> 4, - (((data[5] >> 6) & 0x3) + 4) * 4, (data[5] & 0x7) + 1); - CodecAudioClose(MyAudioDecoder); - - bits_per_sample = (((data[5] >> 6) & 0x3) + 4) * 4; - if (bits_per_sample != 16) { - Error(_ - ("softhddev: LPCM %d bits per sample aren't supported\n"), - bits_per_sample); - // FIXME: handle unsupported formats. - } - samplerate = samplerates[data[5] >> 4]; - channels = (data[5] & 0x7) + 1; - AudioSetup(&samplerate, &channels, 0); - if (samplerate != samplerates[data[5] >> 4]) { - Error(_("softhddev: LPCM %d sample-rate is unsupported\n"), - samplerates[data[5] >> 4]); - // FIXME: support resample - } - if (channels != (data[5] & 0x7) + 1) { - Error(_("softhddev: LPCM %d channels are unsupported\n"), - (data[5] & 0x7) + 1); - // FIXME: support resample - } - //CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_PCM_DVD); - AudioCodecID = CODEC_ID_PCM_DVD; + if (channels != (p[5] & 0x7) + 1) { + Error(_("softhddev: LPCM %d channels are unsupported\n"), + (p[5] & 0x7) + 1); + // FIXME: support resample } - } else { - // no start package - // FIXME: Nick/Viva sends this shit, need to find sync in packet - // FIXME: otherwise it takes too long until sound appears - - if (AudioCodecID == CODEC_ID_NONE) { - int codec_id; - - Debug(3, "[softhddev]%s: ??? %d\n", __FUNCTION__, id); - avpkt->data = (void *)data; - avpkt->size = size; - if (AudioChannelID == 0xBD) { - n = FindDolbySync(avpkt); - codec_id = CODEC_ID_AC3; - AudioRawAc3 = 1; - } else if (0xC0 <= AudioChannelID && AudioChannelID <= 0xDF) { - n = FindAudioSync(avpkt); - codec_id = CODEC_ID_MP2; - } else { - n = -1; - } - if (n < 0) { - return osize; - } + //CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_PCM_DVD); + AudioCodecID = CODEC_ID_PCM_DVD; + } - CodecAudioOpen(MyAudioDecoder, NULL, codec_id); - AudioCodecID = codec_id; - data += n; - size -= n; - avpkt->pts = AV_NOPTS_VALUE; - } + if (AudioAvPkt->pts != (int64_t) AV_NOPTS_VALUE) { + AudioSetClock(AudioAvPkt->pts); + AudioAvPkt->pts = AV_NOPTS_VALUE; } - // still no decoder or codec known + swab(p + 7, AudioAvPkt->data, n - 7); + AudioEnqueue(AudioAvPkt->data, n - 7); + + return size; + } + // DVD track header + if ((id & 0xF0) == 0x80 && (p[0] & 0xF0) == 0x80) { + p += 4; + n -= 4; // skip track header if (AudioCodecID == CODEC_ID_NONE) { - return osize; + AudioSetBufferTime(400); } } - // convert data, if needed for ffmpeg - if (AudioCodecID == CODEC_ID_AC3 && !AudioRawAc3 - && (data[0] & 0xF0) == 0x80) { - avpkt->data = (void *)data + 4; // skip track header - avpkt->size = size - 4; - if (avpkt->pts != (int64_t) AV_NOPTS_VALUE) { - avpkt->pts += 200 * 90; // FIXME: needs bigger buffer + // append new packet, to partial old data + memcpy(AudioAvPkt->data + AudioAvPkt->stream_index, p, n); + AudioAvPkt->stream_index += n; + + n = AudioAvPkt->stream_index; + p = AudioAvPkt->data; + while (n >= 5) { + int r; + unsigned codec_id; + + // 4 bytes 0xFFExxxxx Mpeg audio + // 3 bytes 0x56Exxx AAC LATM audio + // 5 bytes 0x0B77xxxxxx AC3 audio + // PCM audio can't be found + r = 0; + codec_id = CODEC_ID_NONE; // keep compiler happy + if (id != 0xbd && FastMpegCheck(p)) { + r = MpegCheck(p, n); + codec_id = CODEC_ID_MP2; } - CodecAudioDecode(MyAudioDecoder, avpkt); - } else if (AudioCodecID == CODEC_ID_PCM_DVD) { - if (size > 7) { - char *buf; - - if (avpkt->pts != (int64_t) AV_NOPTS_VALUE) { - // FIXME: needs bigger buffer - AudioSetClock(avpkt->pts + 200 * 90); - } - if (!(buf = malloc(size - 7))) { - Error(_("softhddev: out of memory\n")); - } else { - swab(data + 7, buf, size - 7); - AudioEnqueue(buf, size - 7); - free(buf); + if (id != 0xbd && !r && FastLatmCheck(p)) { + r = LatmCheck(p, n); + codec_id = CODEC_ID_AAC_LATM; + } + if ((id == 0xbd || (id & 0xF0) == 0x80) && !r && FastAc3Check(p)) { + r = Ac3Check(p, n); + codec_id = CODEC_ID_AC3; + } + if (r < 0) { // need more bytes + break; + } + if (r > 0) { + AVPacket avpkt[1]; + + // new codec id, close and open new + if (AudioCodecID != codec_id) { + CodecAudioClose(MyAudioDecoder); + CodecAudioOpen(MyAudioDecoder, NULL, codec_id); + AudioCodecID = codec_id; } + av_init_packet(avpkt); + avpkt->data = (void *)p; + avpkt->size = r; + avpkt->pts = AudioAvPkt->pts; + avpkt->dts = AudioAvPkt->dts; + CodecAudioDecode(MyAudioDecoder, avpkt); + AudioAvPkt->pts = AV_NOPTS_VALUE; + AudioAvPkt->dts = AV_NOPTS_VALUE; + p += r; + n -= r; + continue; } - } else { - avpkt->data = (void *)data; - avpkt->size = size; - CodecAudioDecode(MyAudioDecoder, avpkt); + ++p; + --n; + } + + // copy remaining bytes to start of packet + if (n) { + memmove(AudioAvPkt->data, p, n); } + AudioAvPkt->stream_index = n; - return osize; + return size; } +#ifdef USE_TS_AUDIO + +/** +** Play transport stream audio packet. +** +** @param data data of exactly one complete TS packet +** @param size size of TS packet (always TS_PACKET_SIZE) +** +** @returns number of bytes consumed; +*/ +int PlayTsAudio(const uint8_t * data, int size) +{ + static TsDemux tsdx[1]; + + if (StreamFreezed) { // stream freezed + return 0; + } + if (SkipAudio || !MyAudioDecoder) { // skip audio + return size; + } + // Don't overrun audio buffers on replay + if (AudioFreeBytes() < 3072 * 8 * 8) { // 8 channels 8 packets + return 0; + } + + if (NewAudioStream) { + // FIXME: does this clear the audio ringbuffer? + CodecAudioClose(MyAudioDecoder); + AudioCodecID = CODEC_ID_NONE; + NewAudioStream = 0; + PesReset(PesDemuxAudio); + } + return TsDemuxer(tsdx, data, size); +} + +#endif + /** ** Set volume of audio device. ** @@ -495,14 +1099,13 @@ static enum CodecID VideoCodecID; ///< current codec id static const char *X11DisplayName; ///< x11 display name static volatile char Usr1Signal; ///< true got usr1 signal - /// video PES buffer default size -#define VIDEO_BUFFER_SIZE (512 * 1024) -#define VIDEO_PACKET_MAX 128 ///< max number of video packets +#define VIDEO_BUFFER_SIZE (512 * 1024) ///< video PES buffer default size +#define VIDEO_PACKET_MAX 192 ///< max number of video packets /// video PES packet ring buffer static AVPacket VideoPacketRb[VIDEO_PACKET_MAX]; static int VideoPacketWrite; ///< write pointer static int VideoPacketRead; ///< read pointer -atomic_t VideoPacketsFilled; ///< how many of the buffer is used +static atomic_t VideoPacketsFilled; ///< how many of the buffer is used static volatile char VideoClearBuffers; ///< clear video buffers static volatile char SkipVideo; ///< skip video @@ -813,6 +1416,14 @@ int VideoDecode(void) } /** +** Get number of video buffers. +*/ +int VideoGetBuffers(void) +{ + return atomic_read(&VideoPacketsFilled); +} + +/** ** Try video start. ** ** NOT TRUE: Could be called, when already started. @@ -949,9 +1560,13 @@ int PlayVideo(const uint8_t * data, int size) return size; } n = data[8]; // header size - // wrong size - if (size < 9 + n + 4) { - Error(_("[softhddev] invalid video packet %d bytes\n"), size); + + if (size < 9 + n + 4) { // wrong size + if (size == 9 + n) { + Warning(_("[softhddev] empty video packet\n")); + } else { + Error(_("[softhddev] invalid video packet %d bytes\n"), size); + } return size; } // buffer full: needed for replay @@ -1374,11 +1989,11 @@ void StillPicture(const uint8_t * data, int size) int Poll(int timeout) { // buffers are too full - if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX / 2) { + if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX * 2 / 3) { if (timeout) { // let display thread work usleep(timeout * 1000); } - return atomic_read(&VideoPacketsFilled) < VIDEO_PACKET_MAX / 2; + return atomic_read(&VideoPacketsFilled) < VIDEO_PACKET_MAX * 2 / 3; } return 1; } @@ -1645,6 +2260,7 @@ void SoftHdDeviceExit(void) MyAudioDecoder = NULL; } NewAudioStream = 0; + av_free_packet(AudioAvPkt); StopVideo(); @@ -1702,8 +2318,10 @@ void Start(void) if (!ConfigStartSuspended) { // FIXME: AudioInit for HDMI after X11 startup AudioInit(); + av_new_packet(AudioAvPkt, AUDIO_BUFFER_SIZE); MyAudioDecoder = CodecAudioNewDecoder(); AudioCodecID = CODEC_ID_NONE; + AudioChannelID = -1; if (!ConfigStartX11Server) { StartVideo(); @@ -1713,6 +2331,10 @@ void Start(void) SkipAudio = 1; } pthread_mutex_init(&SuspendLockMutex, NULL); + +#ifdef USE_TS_AUDIO + PesInit(PesDemuxAudio); +#endif } /** @@ -1770,6 +2392,7 @@ void Suspend(int video, int audio, int dox11) MyAudioDecoder = NULL; } NewAudioStream = 0; + av_free_packet(AudioAvPkt); } if (video) { StopVideo(); @@ -1801,8 +2424,10 @@ void Resume(void) } if (!MyAudioDecoder) { // audio not running AudioInit(); + av_new_packet(AudioAvPkt, AUDIO_BUFFER_SIZE); MyAudioDecoder = CodecAudioNewDecoder(); AudioCodecID = CODEC_ID_NONE; + AudioChannelID = -1; } SkipVideo = 0; |