diff options
-rw-r--r-- | HISTORY | 16 | ||||
-rw-r--r-- | audio.c | 729 | ||||
-rw-r--r-- | audio.h | 37 | ||||
-rw-r--r-- | omx.c | 165 | ||||
-rw-r--r-- | omx.h | 28 | ||||
-rw-r--r-- | omxdevice.c | 328 | ||||
-rw-r--r-- | omxdevice.h | 44 | ||||
-rw-r--r-- | ovgosd.c | 5 | ||||
-rw-r--r-- | po/de_DE.po | 7 | ||||
-rw-r--r-- | rpihddevice.c | 6 | ||||
-rw-r--r-- | setup.c | 3 | ||||
-rw-r--r-- | types.h | 4 |
12 files changed, 834 insertions, 538 deletions
@@ -1,6 +1,22 @@ VDR Plugin 'rpihddevice' Revision History ----------------------------------------- +2013-12-15: Version 0.0.6 +------------------------- +- new: + - still picture + - trick speeds +- fixed: + - reworked audio detection and decoding, fixed several issues + - reworked stream starting behavior, fixed audio-/video-only play back + - fixed several issues with unsupported video codec (e.g. without MPEG2 key) + - improved fast forward/reverse mode + - several minor bugfixes +- missing: + - deinterlacer + - image grabbing + - video format/output options + 2013-11-17: Version 0.0.5 ------------------------- - new: @@ -13,6 +13,8 @@ #include <string.h> +#define AVPKT_BUFFER_SIZE (64 * 1024) /* 1 PES packet */ + class cAudioParser { @@ -21,13 +23,40 @@ public: cAudioParser() { } ~cAudioParser() { } - inline AVPacket* Packet(void) { return &m_packet; } - inline unsigned int Size(void) { return m_packet.stream_index; } - inline unsigned char *Data(void) { return m_packet.data; } + AVPacket* Packet(void) + { + return &m_packet; + } + + cAudioCodec::eCodec GetCodec(void) + { + if (!m_parsed) + Parse(); + return m_codec; + } + + unsigned int GetChannels(void) + { + if (!m_parsed) + Parse(); + return m_channels; + } + + bool Empty(void) + { + if (!m_parsed) + Parse(); + return m_packet.size == 0; + } int Init(void) { - return av_new_packet(&m_packet, 64 * 1024 /* 1 PES packet */); + if (!av_new_packet(&m_packet, AVPKT_BUFFER_SIZE)) + { + Reset(); + return 0; + } + return -1; } int DeInit(void) @@ -38,42 +67,66 @@ public: void Reset(void) { - m_packet.stream_index = 0; + m_codec = cAudioCodec::eInvalid; + m_channels = 0; + m_packet.size = 0; + m_size = 0; + m_parsed = false; memset(m_packet.data, 0, FF_INPUT_BUFFER_PADDING_SIZE); } bool Append(const unsigned char *data, unsigned int length) { - if (m_packet.stream_index + length + FF_INPUT_BUFFER_PADDING_SIZE > m_packet.size) + if (m_size + length + FF_INPUT_BUFFER_PADDING_SIZE > AVPKT_BUFFER_SIZE) return false; - memcpy(m_packet.data + m_packet.stream_index, data, length); - m_packet.stream_index += length; - memset(m_packet.data + m_packet.stream_index, 0, FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(m_packet.data + m_size, data, length); + m_size += length; + memset(m_packet.data + m_size, 0, FF_INPUT_BUFFER_PADDING_SIZE); + + m_parsed = false; return true; } void Shrink(unsigned int length) { - if (length < m_packet.stream_index) + if (length < m_size) { - memmove(m_packet.data, m_packet.data + length, m_packet.stream_index - length); - m_packet.stream_index -= length; - memset(m_packet.data + m_packet.stream_index, 0, FF_INPUT_BUFFER_PADDING_SIZE); + memmove(m_packet.data, m_packet.data + length, m_size - length); + m_size -= length; + memset(m_packet.data + m_size, 0, FF_INPUT_BUFFER_PADDING_SIZE); + + m_parsed = false; } else Reset(); } - cAudioCodec::eCodec Parse(unsigned int &offset) +private: + + // Check format of first audio packet in buffer. If format has been + // guessed, but packet is not yet complete, codec is set with a length + // of 0. Once the buffer contains either the exact amount of expected + // data or another valid packet start after the first frame, packet + // size is set to the first frame length. + // Valid packets are always moved to the buffer start, if no valid + // audio frame has been found, packet gets cleared. + // + // To do: + // - parse sampling rate to allow non-48kHz audio + // - consider codec change for next frame check + + void Parse() { cAudioCodec::eCodec codec = cAudioCodec::eInvalid; - - while (Size() - offset >= 5) + int channels = 0; + int offset = 0; + int frameSize = 0; + + while (m_size - offset >= 3) { - const uint8_t *p = Data() + offset; - int n = Size() - offset; - int r = 0; + const uint8_t *p = m_packet.data + offset; + int n = m_size - offset; // 4 bytes 0xFFExxxxx MPEG audio // 3 bytes 0x56Exxx AAC LATM audio @@ -84,49 +137,73 @@ public: if (FastMpegCheck(p)) { - r = MpegCheck(p, n); - codec = cAudioCodec::eMPG; + if (MpegCheck(p, n, frameSize)) + { + codec = cAudioCodec::eMPG; + channels = 2; + } + break; } else if (FastAc3Check(p)) { - r = Ac3Check(p, n); - codec = cAudioCodec::eAC3; - - if (r > 0 && p[5] > (10 << 3)) - codec = cAudioCodec::eEAC3; + if (Ac3Check(p, n, frameSize, channels)) + { + codec = cAudioCodec::eAC3; + if (n > 5 && p[5] > (10 << 3)) + codec = cAudioCodec::eEAC3; + } + break; } else if (FastLatmCheck(p)) { - r = LatmCheck(p, n); - codec = cAudioCodec::eAAC; + if (LatmCheck(p, n, frameSize)) + { + codec = cAudioCodec::eAAC; + channels = 2; + } + break; } else if (FastAdtsCheck(p)) { - r = AdtsCheck(p, n); - codec = cAudioCodec::eDTS; + if (AdtsCheck(p, n, frameSize, channels)) + codec = cAudioCodec::eADTS; + break; } - if (r < 0) // need more bytes - break; + ++offset; + } - if (r > 0) - return codec; + if (codec != cAudioCodec::eInvalid) + { + if (offset) + { + dsyslog("rpihddevice: audio packet shrinked by %d bytes", offset); + Shrink(offset); + } - ++offset; + m_codec = codec; + m_channels = channels; + m_packet.size = frameSize; } - return cAudioCodec::eInvalid; - } + else + Reset(); -private: + m_parsed = true; + } - AVPacket m_packet; + AVPacket m_packet; + cAudioCodec::eCodec m_codec; + unsigned int m_channels; + unsigned int m_size; + bool m_parsed; /* ------------------------------------------------------------------------- */ /* audio codec parser helper functions, taken from vdr-softhddevice */ /* ------------------------------------------------------------------------- */ static const uint16_t BitRateTable[2][3][16]; - static const uint16_t SampleRateTable[4]; + static const uint16_t MpegSampleRateTable[4]; + static const uint16_t Ac3SampleRateTable[4]; static const uint16_t Ac3FrameSizeTable[38][3]; /// @@ -155,13 +232,6 @@ private: /// /// 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 @@ -170,7 +240,7 @@ private: /// 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 f 2x SampleRate index (41000, 48000, 32000, 0) /// o g 1x Padding bit /// o .. Doesn't care /// @@ -180,24 +250,27 @@ private: /// Layer II & III: /// FrameLengthInBytes = 144 * BitRate / SampleRate + Padding /// - static int MpegCheck(const uint8_t *data, int size) + static bool MpegCheck(const uint8_t *data, int size, int &frame_size) { - int frame_size; + frame_size = 0; + if (size < 3) + return true; + int mpeg2 = !(data[1] & 0x08) && (data[1] & 0x10); int mpeg25 = !(data[1] & 0x08) && !(data[1] & 0x10); int layer = 4 - ((data[1] >> 1) & 0x03); int padding = (data[2] >> 1) & 0x01; - int sample_rate = SampleRateTable[(data[2] >> 2) & 0x03]; + int sample_rate = MpegSampleRateTable[(data[2] >> 2) & 0x03]; if (!sample_rate) - return 0; + return false; sample_rate >>= mpeg2; // MPEG 2 half rate sample_rate >>= mpeg25; // MPEG 2.5 quarter rate int bit_rate = BitRateTable[mpeg2 | mpeg25][layer - 1][(data[2] >> 4) & 0x0F]; if (!bit_rate) - return 0; + return false; switch (layer) { @@ -212,14 +285,14 @@ private: frame_size = frame_size + padding; break; } - if (frame_size + 4 > size) - return -frame_size - 4; - // check if after this frame a new MPEG frame starts - if (cAudioParser::FastMpegCheck(data + frame_size)) - return frame_size; + if (size >= frame_size + 3 && !FastMpegCheck(data + frame_size)) + return false; - return 0; + if (frame_size > size) + frame_size = 0; + + return true; } /// @@ -241,22 +314,16 @@ private: /// /// 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 - /// /// o AC-3 Header - /// AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF + /// AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF GGGxxxxx /// /// o a 16x Frame sync, always 0x0B77 /// o b 16x CRC 16 - /// o c 2x Sample rate + /// o c 2x Sample rate ( 48000, 44100, 32000, reserved ) /// o d 6x Frame size code /// o e 5x Bit stream ID /// o f 3x Bit stream mode + /// o g 3x Audio coding mode /// /// o E-AC-3 Header /// AAAAAAAA AAAAAAAA BBCCCDDD DDDDDDDD EEFFGGGH IIIII... @@ -267,43 +334,76 @@ private: /// o d 10x Frame size - 1 in words /// o e 2x Frame size code /// o f 2x Frame size code 2 + /// o g 3x Channel mode + /// 0 h 1x LFE on /// - static int Ac3Check(const uint8_t *p, int size) + static bool Ac3Check(const uint8_t *p, int size, int &frame_size, int &channels) { - int frame_size; + frame_size = 0; + if (size < 7) + return true; - if (size < 5) // need 5 bytes to see if AC-3/E-AC-3 - return -5; + int acmod; + bool lfe; + int sample_rate; // for future use, E-AC3 t.b.d. - if (p[5] > (10 << 3)) // E-AC-3 + if (p[5] > (10 << 3)) // E-AC-3 { if ((p[4] & 0xF0) == 0xF0) // invalid fscod fscod2 - return 0; + return false; + + acmod = (p[4] & 0x0E) >> 1; // number of channels, LFE excluded + lfe = p[4] & 0x01; frame_size = ((p[2] & 0x03) << 8) + p[3] + 1; frame_size *= 2; } else // AC-3 { + sample_rate = Ac3SampleRateTable[(p[4] >> 6) & 0x03]; + int fscod = p[4] >> 6; if (fscod == 0x03) // invalid sample rate - return 0; + return false; int frmsizcod = p[4] & 0x3F; if (frmsizcod > 37) // invalid frame size - return 0; + return false; + + acmod = p[6] >> 5; // number of channels, LFE excluded + + int lfe_bptr = 51; // position of LFE bit in header for 2.0 + if ((acmod & 0x01) && (acmod != 0x01)) + lfe_bptr += 2; // skip center mix level + if (acmod & 0x04) + lfe_bptr += 2; // skip surround mix level + if (acmod == 0x02) + lfe_bptr += 2; // skip surround mode + lfe = (p[lfe_bptr / 8] & (1 << (7 - (lfe_bptr % 8)))); // invalid is checked above frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2; } - if (frame_size + 5 > size) - return -frame_size - 5; - // check if after this frame a new AC-3 frame starts - if (FastAc3Check(p + frame_size)) - return frame_size; + channels = + acmod == 0x00 ? 2 : // Ch1, Ch2 + acmod == 0x01 ? 1 : // C + acmod == 0x02 ? 2 : // L, R + acmod == 0x03 ? 3 : // L, C, R + acmod == 0x04 ? 3 : // L, R, S + acmod == 0x05 ? 4 : // L, C, R, S + acmod == 0x06 ? 4 : // L, R, RL, RR + acmod == 0x07 ? 5 : 0; // L, C, R, RL, RR - return 0; + if (lfe) channels++; + + if (size >= frame_size + 2 && !FastAc3Check(p + frame_size)) + return false; + + if (frame_size > size) + frame_size = 0; + + return true; } /// @@ -325,27 +425,23 @@ private: /// /// 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 *p, int size) + static bool LatmCheck(const uint8_t *p, int size, int &frame_size) { + frame_size = 0; + if (size < 3) + return true; + // 13 bit frame size without header - int frame_size = ((p[1] & 0x1F) << 8) + p[2]; + frame_size = ((p[1] & 0x1F) << 8) + p[2]; frame_size += 3; - if (frame_size + 2 > size) - return -frame_size - 2; + if (size >= frame_size + 3 && !FastLatmCheck(p + frame_size)) + return false; - // check if after this frame a new AAC LATM frame starts - if (FastLatmCheck(p + frame_size)) - return frame_size; + if (frame_size > size) + frame_size = 0; - return 0; + return true; } /// @@ -369,13 +465,6 @@ private: /// /// 0xFFF already checked. /// - /// @param data incomplete PES packet - /// @param size number of bytes - /// - /// @retval <0 possible ADTS audio, but need more data - /// @retval 0 no valid ADTS audio - /// @retval >0 valid AC-3 audio - /// /// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP /// (QQQQQQQQ QQQQQQQ) /// @@ -385,25 +474,40 @@ private: /// o .. /// o F*4 sampling frequency index (15 is invalid) /// o .. + /// o H*3 MPEG-4 channel configuration + /// o ... /// o M*13 frame length /// - static int AdtsCheck(const uint8_t *p, int size) + static bool AdtsCheck(const uint8_t *p, int size, int &frame_size, int &channels) { - if (size < 6) - return -6; + frame_size = 0; + if (size < 6) + return true; - int frame_size = (p[3] & 0x03) << 11; + frame_size = (p[3] & 0x03) << 11; frame_size |= (p[4] & 0xFF) << 3; frame_size |= (p[5] & 0xE0) >> 5; - if (frame_size + 3 > size) - return -frame_size - 3; + int ch_config = (p[2] & 0x01) << 7; + ch_config |= (p[3] & 0xC0) >> 6; + channels = + ch_config == 0x00 ? 0 : // defined in AOT specific config + ch_config == 0x01 ? 1 : // C + ch_config == 0x02 ? 2 : // L, R + ch_config == 0x03 ? 3 : // C, L, R + ch_config == 0x04 ? 4 : // C, L, R, RC + ch_config == 0x05 ? 5 : // C, L, R, RL, RR + ch_config == 0x06 ? 6 : // C, L, R, RL, RR, LFE + ch_config == 0x07 ? 8 : // C, L, R, SL, SR, RL, RR, LFE + 0; + + if (size >= frame_size + 3 && !FastAdtsCheck(p + frame_size)) + return false; - // check if after this frame a new ADTS frame starts - if (FastAdtsCheck(p + frame_size)) - return frame_size; + if (frame_size > size) + frame_size = 0; - return 0; + return true; } }; @@ -429,7 +533,12 @@ const uint16_t cAudioParser::BitRateTable[2][3][16] = /// /// MPEG sample rate table. /// -const uint16_t cAudioParser::SampleRateTable[4] = { 44100, 48000, 32000, 0 }; +const uint16_t cAudioParser::MpegSampleRateTable[4] = { 44100, 48000, 32000, 0 }; + +/// +/// AC-3 sample rate table. +/// +const uint16_t cAudioParser::Ac3SampleRateTable[4] = { 48000, 44100, 32000, 0 }; /// /// Possible AC-3 frame sizes. @@ -454,17 +563,12 @@ const uint16_t cAudioParser::Ac3FrameSizeTable[38][3] = cAudioDecoder::cAudioDecoder(cOmx *omx) : cThread(), - m_codec(cAudioCodec::eInvalid), - m_outputFormat(cAudioCodec::ePCM), - m_outputPort(cAudioPort::eLocal), - m_channels(0), - m_samplingRate(0), m_passthrough(false), - m_outputFormatChanged(true), + m_reset(false), + m_ready(false), m_pts(0), - m_frame(0), m_mutex(new cMutex()), - m_newData(new cCondWait()), + m_wait(new cCondWait()), m_parser(new cAudioParser()), m_omx(omx) { @@ -473,7 +577,7 @@ cAudioDecoder::cAudioDecoder(cOmx *omx) : cAudioDecoder::~cAudioDecoder() { delete m_parser; - delete m_newData; + delete m_wait; delete m_mutex; } @@ -490,7 +594,7 @@ int cAudioDecoder::Init(void) m_codecs[cAudioCodec::eAC3 ].codec = avcodec_find_decoder(CODEC_ID_AC3); m_codecs[cAudioCodec::eEAC3].codec = avcodec_find_decoder(CODEC_ID_EAC3); m_codecs[cAudioCodec::eAAC ].codec = avcodec_find_decoder(CODEC_ID_AAC_LATM); - m_codecs[cAudioCodec::eDTS ].codec = avcodec_find_decoder(CODEC_ID_DTS); + m_codecs[cAudioCodec::eADTS].codec = avcodec_find_decoder(CODEC_ID_AAC); for (int i = 0; i < cAudioCodec::eNumCodecs; i++) { @@ -513,14 +617,6 @@ int cAudioDecoder::Init(void) } } - m_frame = avcodec_alloc_frame(); - if (!m_frame) - { - esyslog("rpihddevice: failed to allocate audio frame!"); - ret = -1; - } - avcodec_get_frame_defaults(m_frame); - if (ret < 0) DeInit(); @@ -540,41 +636,42 @@ int cAudioDecoder::DeInit(void) av_free(m_codecs[codec].context); } - av_free(m_frame); m_parser->DeInit(); return 0; } -bool cAudioDecoder::WriteData(const unsigned char *buf, unsigned int length, uint64_t pts) +int cAudioDecoder::WriteData(const unsigned char *buf, unsigned int length, uint64_t pts) { - m_mutex->Lock(); - - bool ret = m_parser->Append(buf, length); - if (ret) + int ret = 0; + if (m_ready) { - // set current pts as reference - m_pts = pts; - - ProbeCodec(); - m_newData->Signal(); + m_mutex->Lock(); + if (m_parser->Append(buf, length)) + { + m_pts = pts; + if (!m_parser->Empty()) + { + m_ready = false; + m_wait->Signal(); + } + ret = length; + } + m_mutex->Unlock(); } - - m_mutex->Unlock(); return ret; } void cAudioDecoder::Reset(void) { m_mutex->Lock(); - m_parser->Reset(); - m_codec = cAudioCodec::eInvalid; - avcodec_get_frame_defaults(m_frame); + m_reset = true; + m_wait->Signal(); m_mutex->Unlock(); } bool cAudioDecoder::Poll(void) { - return !m_parser->Size() && m_omx->PollAudioBuffers(); + return m_ready && m_omx->PollAudioBuffers(); } void cAudioDecoder::Action(void) @@ -583,214 +680,232 @@ void cAudioDecoder::Action(void) while (Running()) { - if (m_parser->Size()) + unsigned int channels = 0; + unsigned int outputChannels = 0; + + bool bufferFull = false; + + cAudioCodec::eCodec codec = cAudioCodec::eInvalid; + OMX_BUFFERHEADERTYPE *buf = 0; + + AVFrame *frame = avcodec_alloc_frame(); + if (!frame) { - m_mutex->Lock(); - if (m_outputFormatChanged) - { - m_outputFormatChanged = false; - m_omx->SetupAudioRender( - m_outputFormat, m_channels, - m_samplingRate, m_outputPort); - } + esyslog("rpihddevice: failed to allocate audio frame!"); + return; + } - OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(m_pts); - if (buf) + m_reset = false; + m_ready = true; + + while (!m_reset) + { + // check for data if no decoded samples are pending + if (!m_parser->Empty() && !frame->nb_samples) { - while (DecodeFrame()) - buf->nFilledLen += ReadFrame( - buf->pBuffer + buf->nFilledLen, - buf->nAllocLen - buf->nFilledLen); + if (codec != m_parser->GetCodec() || + channels != m_parser->GetChannels()) + { + // to change codec config, we need to empty buffer first + if (buf) + bufferFull = true; + else + { + codec = m_parser->GetCodec(); + channels = m_parser->GetChannels(); - if (!m_omx->EmptyAudioBuffer(buf)) - esyslog("rpihddevice: failed to empty audio buffer!"); + outputChannels = channels; + SetCodec(codec, outputChannels); + } + } } - else + + // if codec has been configured but we don't have a buffer, get one + while (codec != cAudioCodec::eInvalid && !buf && !m_reset) { - esyslog("rpihddevice: failed to get audio buffer!"); - cCondWait::SleepMs(5); + buf = m_omx->GetAudioBuffer(m_pts); + if (buf) + m_pts = 0; + else + m_wait->Wait(10); } - m_mutex->Unlock(); - } - else - m_newData->Wait(50); - } - dsyslog("rpihddevice: cAudioDecoder() thread ended"); -} -unsigned int cAudioDecoder::DecodeFrame() -{ - unsigned int ret = 0; + // we have a non-full buffer and data to encode / copy + if (buf && !bufferFull && !m_parser->Empty()) + { + int copied = 0; + if (m_passthrough) + { + // for pass-through directly copy AV packet to buffer + if (m_parser->Packet()->size <= buf->nAllocLen - buf->nFilledLen) + { + m_mutex->Lock(); - if (m_passthrough) - ret = m_parser->Size(); + memcpy(buf->pBuffer + buf->nFilledLen, + m_parser->Packet()->data, m_parser->Packet()->size); + buf->nFilledLen += m_parser->Packet()->size; + m_parser->Shrink(m_parser->Packet()->size); - else if (m_parser->Size()) - { - int frame = 0; - int len = avcodec_decode_audio4(m_codecs[m_codec].context, - m_frame, &frame, m_parser->Packet()); + m_mutex->Unlock(); + } + else + if (m_parser->Packet()->size > buf->nAllocLen) + { + esyslog("rpihddevice: encoded audio frame too big!"); + m_reset = true; + break; + } + else + bufferFull = true; + } + else + { + // decode frame if we do not pass-through + m_mutex->Lock(); - // decoding error or number of channels changed ? - if (len < 0 || m_channels != m_codecs[m_codec].context->channels) - { - m_parser->Reset(); - m_codec = cAudioCodec::eInvalid; - } - else - { - m_parser->Shrink(len); - ret = frame ? len : 0; - } - } - return ret; -} + int gotFrame = 0; + int len = avcodec_decode_audio4(m_codecs[codec].context, + frame, &gotFrame, m_parser->Packet()); -unsigned int cAudioDecoder::ReadFrame(unsigned char *buf, unsigned int bufsize) -{ - unsigned int ret = 0; + if (len > 0) + m_parser->Shrink(len); - if (m_passthrough) - { - // for pass-through directly read from AV packet - if (m_parser->Size() > bufsize) - ret = bufsize; - else - ret = m_parser->Size(); + m_mutex->Unlock(); - memcpy(buf, m_parser->Data(), ret); - m_parser->Shrink(ret); - } - else - { - if (m_frame->nb_samples > 0) - { - ret = av_samples_get_buffer_size(NULL, - m_channels == 6 ? 8 : m_channels, m_frame->nb_samples, - m_codecs[m_codec].context->sample_fmt, 1); - - if (ret > bufsize) - { - esyslog("rpihddevice: decoded audio frame too big!"); - ret = 0; + if (len < 0) + { + esyslog("rpihddevice: failed to decode audio frame!"); + m_reset = true; + break; + } + } } - else + + // we have decoded samples we need to copy to buffer + if (buf && !bufferFull && frame->nb_samples > 0) { - if (m_channels == 6) - { - // interleaved copy to fit 5.1 data into 8 channels - int32_t* src = (int32_t*)m_frame->data[0]; - int32_t* dst = (int32_t*)buf; + int length = av_samples_get_buffer_size(NULL, + outputChannels == 6 ? 8 : outputChannels, frame->nb_samples, + m_codecs[codec].context->sample_fmt, 1); - for (int i = 0; i < m_frame->nb_samples; i++) + if (length <= buf->nAllocLen - buf->nFilledLen) + { + if (outputChannels == 6) { - *dst++ = *src++; // LF & RF - *dst++ = *src++; // CF & LFE - *dst++ = *src++; // LR & RR - *dst++ = 0; // empty channels + // interleaved copy to fit 5.1 data into 8 channels + int32_t* src = (int32_t*)frame->data[0]; + int32_t* dst = (int32_t*)buf->pBuffer + buf->nFilledLen; + + for (int i = 0; i < frame->nb_samples; i++) + { + *dst++ = *src++; // LF & RF + *dst++ = *src++; // CF & LFE + *dst++ = *src++; // LR & RR + *dst++ = 0; // empty channels + } } + else + memcpy(buf->pBuffer + buf->nFilledLen, frame->data[0], length); + + buf->nFilledLen += length; + frame->nb_samples = 0; } else - memcpy(buf, m_frame->data[0], ret); + { + if (length > buf->nAllocLen) + { + esyslog("rpihddevice: decoded audio frame too big!"); + m_reset = true; + break; + } + else + bufferFull = true; + } } - } - } - return ret; -} -bool cAudioDecoder::ProbeCodec(void) -{ - bool ret = false; - - unsigned int offset = 0; - cAudioCodec::eCodec codec = m_parser->Parse(offset); - - if (codec != cAudioCodec::eInvalid) - { - if (offset) - m_parser->Shrink(offset); - - // if new codec has been found, decode one packet to determine number of - // channels, since they are needed to properly set audio output format - if (codec != m_codec || cRpiSetup::HasAudioSetupChanged()) - { - m_codecs[codec].context->flags |= CODEC_FLAG_TRUNCATED; - m_codecs[codec].context->request_channel_layout = AV_CH_LAYOUT_NATIVE; - m_codecs[codec].context->request_channels = 0; - - int frame = 0; - avcodec_get_frame_defaults(m_frame); - int len = avcodec_decode_audio4(m_codecs[codec].context, m_frame, - &frame, m_parser->Packet()); + // check if no decoded samples are pending and parser is empty + if (!frame->nb_samples && m_parser->Empty()) + { + // if no more data but buffer with data -> end of PES packet + if (buf && buf->nFilledLen > 0) + bufferFull = true; + else + if (m_ready) + m_wait->Wait(50); + } - if (len > 0 && frame) + // we have a buffer to empty + if (buf && bufferFull) { - SetCodec(codec); - ret = true; + if (m_omx->EmptyAudioBuffer(buf)) + { + bufferFull = false; + buf = 0; + + // if parser is empty, get new data + if (m_parser->Empty()) + m_ready = true; + } + else + { + esyslog("rpihddevice: failed to empty audio buffer!"); + m_reset = true; + break; + } } } + + dsyslog("reset"); + if (buf && m_omx->EmptyAudioBuffer(buf)) + buf = 0; + + av_free(frame); + m_parser->Reset(); } - return ret; + dsyslog("rpihddevice: cAudioDecoder() thread ended"); } -void cAudioDecoder::SetCodec(cAudioCodec::eCodec codec) +void cAudioDecoder::SetCodec(cAudioCodec::eCodec codec, unsigned int &channels) { - if (codec != cAudioCodec::eInvalid) + if (codec != cAudioCodec::eInvalid && channels > 0) { - if (m_codec == cAudioCodec::eInvalid) - m_outputFormatChanged = true; + dsyslog("rpihddevice: set audio codec to %dch %s", + channels, cAudioCodec::Str(codec)); - m_codec = codec; - m_codecs[m_codec].context->request_channel_layout = AV_CH_LAYOUT_NATIVE; - m_codecs[m_codec].context->request_channels = 0; + m_codecs[codec].context->request_channel_layout = AV_CH_LAYOUT_NATIVE; + m_codecs[codec].context->request_channels = 0; m_passthrough = false; cAudioCodec::eCodec outputFormat = cAudioCodec::ePCM; cAudioPort::ePort outputPort = cAudioPort::eLocal; - int channels = m_codecs[m_codec].context->channels; - int samplingRate = m_codecs[m_codec].context->sample_rate; - - dsyslog("rpihddevice: set audio codec to %s with %d channels, %dHz", - cAudioCodec::Str(m_codec), channels, samplingRate); - if (cRpiSetup::GetAudioPort() == cAudioPort::eHDMI && - cRpiSetup::IsAudioFormatSupported(cAudioCodec::ePCM, - m_codecs[m_codec].context->channels, - m_codecs[m_codec].context->sample_rate)) + cRpiSetup::IsAudioFormatSupported(cAudioCodec::ePCM, channels, 48000)) { outputPort = cAudioPort::eHDMI; if (cRpiSetup::IsAudioPassthrough() && - cRpiSetup::IsAudioFormatSupported(m_codec, - m_codecs[m_codec].context->channels, - m_codecs[m_codec].context->sample_rate)) + cRpiSetup::IsAudioFormatSupported(codec, channels, 48000)) { m_passthrough = true; - outputFormat = m_codec; + outputFormat = codec; } } else { - m_codecs[m_codec].context->request_channel_layout = AV_CH_LAYOUT_STEREO_DOWNMIX; - m_codecs[m_codec].context->request_channels = 2; + m_codecs[codec].context->request_channel_layout = AV_CH_LAYOUT_STEREO_DOWNMIX; + m_codecs[codec].context->request_channels = 2; channels = 2; - } - if ((outputFormat != m_outputFormat) || - (outputPort != m_outputPort ) || - (channels != m_channels ) || - (samplingRate != m_samplingRate)) - { - m_outputFormat = outputFormat; - m_outputPort = outputPort; - m_channels = channels; - m_samplingRate = samplingRate; - m_outputFormatChanged = true; - - dsyslog("rpihddevice: set %s audio output format to %s%s", - cAudioPort::Str(m_outputPort), cAudioCodec::Str(m_outputFormat), - m_passthrough ? " (pass-through)" : ""); + // if 2ch PCM audio on HDMI is supported + if (cRpiSetup::GetAudioPort() == cAudioPort::eHDMI && + cRpiSetup::IsAudioFormatSupported(cAudioCodec::ePCM, 2, 48000)) + outputPort = cAudioPort::eHDMI; } + + m_omx->SetupAudioRender(outputFormat, channels, outputPort, 48000); + dsyslog("rpihddevice: set %s audio output format to %dch %s%s", + cAudioPort::Str(outputPort), channels, cAudioCodec::Str(outputFormat), + m_passthrough ? " (pass-through)" : ""); } } @@ -7,8 +7,7 @@ #ifndef AUDIO_H #define AUDIO_H -extern "C" -{ +extern "C" { #include <libavcodec/avcodec.h> } @@ -30,7 +29,7 @@ public: virtual int Init(void); virtual int DeInit(void); - virtual bool WriteData(const unsigned char *buf, unsigned int length, uint64_t pts = 0); + virtual int WriteData(const unsigned char *buf, unsigned int length, uint64_t pts = 0); virtual bool Poll(void); virtual void Reset(void); @@ -38,12 +37,7 @@ public: protected: virtual void Action(void); - - virtual unsigned int DecodeFrame(); - virtual unsigned int ReadFrame(unsigned char *buf, unsigned int bufsize); - - virtual bool ProbeCodec(void); - void SetCodec(cAudioCodec::eCodec codec); + void SetCodec(cAudioCodec::eCodec codec, unsigned int &channels); struct Codec { @@ -53,21 +47,16 @@ protected: private: - Codec m_codecs[cAudioCodec::eNumCodecs]; - cAudioCodec::eCodec m_codec; - cAudioCodec::eCodec m_outputFormat; - cAudioPort::ePort m_outputPort; - int m_channels; - int m_samplingRate; - bool m_passthrough; - bool m_outputFormatChanged; - uint64_t m_pts; - - AVFrame *m_frame; - cMutex *m_mutex; - cCondWait *m_newData; - cAudioParser *m_parser; - cOmx *m_omx; + Codec m_codecs[cAudioCodec::eNumCodecs]; + bool m_passthrough; + bool m_reset; + bool m_ready; + uint64_t m_pts; + + cMutex *m_mutex; + cCondWait *m_wait; + cAudioParser *m_parser; + cOmx *m_omx; }; #endif @@ -9,8 +9,7 @@ #include <vdr/tools.h> #include <vdr/thread.h> -extern "C" -{ +extern "C" { #include "ilclient.h" } @@ -181,28 +180,28 @@ void cOmx::OnBufferEmpty(void *instance, COMPONENT_T *comp) omx->HandleBufferEmpty(comp); } -void cOmx::OnPortSettingsChanged(void *instance, COMPONENT_T *comp, unsigned int data) +void cOmx::OnPortSettingsChanged(void *instance, COMPONENT_T *comp, OMX_U32 data) { cOmx* omx = static_cast <cOmx*> (instance); omx->HandlePortSettingsChanged(data); } -void cOmx::OnEndOfStream(void *instance, COMPONENT_T *comp, unsigned int data) +void cOmx::OnEndOfStream(void *instance, COMPONENT_T *comp, OMX_U32 data) { cOmx* omx = static_cast <cOmx*> (instance); omx->HandleEndOfStream(data); } -void cOmx::OnError(void *instance, COMPONENT_T *comp, unsigned int data) +void cOmx::OnError(void *instance, COMPONENT_T *comp, OMX_U32 data) { - if (data != OMX_ErrorSameState) + if ((OMX_S32)data != OMX_ErrorSameState) esyslog("rpihddevice: OmxError(%s)", errStr((int)data)); } cOmx::cOmx() : m_mutex(new cMutex()), - m_setVideoStartTime(true), - m_setAudioStartTime(true), + m_setAudioStartTime(false), + m_setVideoStartTime(false), m_setVideoDiscontinuity(false), m_freeAudioBuffers(0), m_freeVideoBuffers(0), @@ -281,6 +280,7 @@ int cOmx::Init(void) // set up the number and size of buffers for audio render m_freeAudioBuffers = 2; //64; + OMX_PARAM_PORTDEFINITIONTYPE param; OMX_INIT_STRUCT(param); param.nPortIndex = 100; @@ -298,8 +298,8 @@ int cOmx::Init(void) if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) esyslog("rpihddevice: failed to get video decoder port parameters!"); - m_freeVideoBuffers = param.nBufferCountActual; + dsyslog("rpihddevice: started with %d video and %d audio buffers", m_freeVideoBuffers, m_freeAudioBuffers); @@ -324,12 +324,7 @@ int cOmx::Init(void) // if (ilclient_enable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL) != 0) // esyslog("rpihddevice: failed to enable port buffer on audio render!"); - OMX_PARAM_BRCMVIDEODECODEERRORCONCEALMENTTYPE ectype; - OMX_INIT_STRUCT(ectype); - ectype.bStartWithValidFrame = OMX_FALSE; - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), - OMX_IndexParamBrcmVideoDecodeErrorConcealment, &ectype) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set video decode error concealment failed\n"); + SetClockState(cOmx::eClockStateRun); return 0; } @@ -432,24 +427,20 @@ void cOmx::SetClockState(eClockState clockState) OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) esyslog("rpihddevice: failed to get clock state!"); -/* dsyslog("rpihddevice: current clock state: %s", - cstate.eState == OMX_TIME_ClockStateRunning ? "Running" : - cstate.eState == OMX_TIME_ClockStateStopped ? "Stopped" : - cstate.eState == OMX_TIME_ClockStateWaitingForStartTime ? "WaitingForStartTime" : - "unknown"); -*/ + // if clock is already running, we need to stop it first if ((cstate.eState == OMX_TIME_ClockStateRunning) && (clockState == eClockStateWaitForVideo || clockState == eClockStateWaitForAudio || clockState == eClockStateWaitForAudioVideo)) { - // clock already running, need to stop it first cstate.eState = OMX_TIME_ClockStateStopped; if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) esyslog("rpihddevice: failed to stop clock!"); } + cstate.nWaitMask = 0; + switch (clockState) { case eClockStateRun: @@ -462,27 +453,28 @@ void cOmx::SetClockState(eClockState clockState) case eClockStateWaitForVideo: cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT0; m_setVideoStartTime = true; - SetClockReference(eClockRefVideo); + cstate.nWaitMask = OMX_CLOCKPORT0; break; case eClockStateWaitForAudio: cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT1; m_setAudioStartTime = true; - SetClockReference(eClockRefAudio); + cstate.nWaitMask = OMX_CLOCKPORT1; break; case eClockStateWaitForAudioVideo: cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT0 | OMX_CLOCKPORT1; - m_setVideoStartTime = true; m_setAudioStartTime = true; - SetClockReference(eClockRefAudio); + m_setVideoStartTime = true; + cstate.nWaitMask = OMX_CLOCKPORT0 | OMX_CLOCKPORT1; break; } + if (cstate.eState == OMX_TIME_ClockStateWaitingForStartTime) + // 200ms pre roll, value taken from omxplayer + cstate.nOffset = ToOmxTicks(-1000LL * 400); + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) esyslog("rpihddevice: failed to set clock state!"); @@ -499,23 +491,32 @@ void cOmx::SetClockScale(float scale) if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), OMX_IndexConfigTimeScale, &scaleType) != OMX_ErrorNone) esyslog("rpihddevice: failed to set clock scale (%d)!", scaleType.xScale); - else - dsyslog("rpihddevice: set clock scale to %.2f (%d)", scale, scaleType.xScale); } -void cOmx::SetMediaTime(uint64_t pts) +void cOmx::SetStartTime(uint64_t pts) { OMX_TIME_CONFIG_TIMESTAMPTYPE timeStamp; OMX_INIT_STRUCT(timeStamp); - timeStamp.nPortIndex = m_clockReference == eClockRefAudio ? 81 : 80; + timeStamp.nPortIndex = 80; //m_clockReference == eClockRefAudio ? 81 : 80; cOmx::PtsToTicks(pts, timeStamp.nTimestamp); if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), - /*m_clockReference == eClockRefAudio ? - OMX_IndexConfigTimeCurrentAudioReference : - OMX_IndexConfigTimeCurrentVideoReference*/ OMX_IndexConfigTimeClientStartTime, &timeStamp) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set %s media clock reference!", + esyslog("rpihddevice: failed to set current start time!"); +} + +void cOmx::SetCurrentReferenceTime(uint64_t pts) +{ + OMX_TIME_CONFIG_TIMESTAMPTYPE timeStamp; + OMX_INIT_STRUCT(timeStamp); + timeStamp.nPortIndex = 80; //OMX_ALL; //m_clockReference == eClockRefAudio ? 81 : 80; + cOmx::PtsToTicks(pts, timeStamp.nTimestamp); + + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), + m_clockReference == eClockRefAudio ? + OMX_IndexConfigTimeCurrentAudioReference : + OMX_IndexConfigTimeCurrentVideoReference, &timeStamp) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set current %s reference time!", m_clockReference == eClockRefAudio ? "audio" : "video"); } @@ -538,6 +539,8 @@ unsigned int cOmx::GetMediaTime(void) void cOmx::SetClockReference(eClockReference clockReference) { + m_mutex->Lock(); + m_clockReference = clockReference; OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE refClock; @@ -548,6 +551,11 @@ void cOmx::SetClockReference(eClockReference clockReference) if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), OMX_IndexConfigTimeActiveRefClock, &refClock) != OMX_ErrorNone) esyslog("rpihddevice: failed set active clock reference!"); + else + dsyslog("rpihddevice: set active clock reference to %s", + m_clockReference == eClockRefAudio ? "audio" : "video"); + + m_mutex->Unlock(); } void cOmx::SetVolume(int vol) @@ -583,8 +591,10 @@ void cOmx::SendEos(void) #endif } -void cOmx::Stop(void) +void cOmx::StopVideo(void) { + dsyslog("rpihddevice: StopVideo()"); + // put video decoder into idle ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle); @@ -600,16 +610,43 @@ void cOmx::Stop(void) ilclient_disable_tunnel(&m_tun[eVideoSchedulerToVideoRender]); ilclient_change_component_state(m_comp[eVideoRender], OMX_StateIdle); + // disable port buffers and allow video decoder to reconfig + ilclient_disable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL); +} + +void cOmx::StopAudio(void) +{ // put audio render onto idle ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); - ilclient_disable_tunnel(&m_tun[eClockToAudioRender]); +// ilclient_disable_tunnel(&m_tun[eClockToAudioRender]); ilclient_change_component_state(m_comp[eAudioRender], OMX_StateIdle); - - // disable port buffers and allow video decoder and audio render to reconfig - ilclient_disable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL); ilclient_disable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL); +} + +void cOmx::SetVideoDataUnitType(eDataUnitType dataUnitType) +{ + OMX_PARAM_DATAUNITTYPE dataUnit; + OMX_INIT_STRUCT(dataUnit); + dataUnit.nPortIndex = 130; + + dataUnit.eEncapsulationType = OMX_DataEncapsulationElementaryStream; + dataUnit.eUnitType = dataUnitType == eCodedPicture ? + OMX_DataUnitCodedPicture : OMX_DataUnitArbitraryStreamSection; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), + OMX_IndexParamBrcmDataUnit, &dataUnit) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder data unit type!"); - SetClockState(eClockStateStop); +} + +void cOmx::SetVideoErrorConcealment(bool startWithValidFrame) +{ + OMX_PARAM_BRCMVIDEODECODEERRORCONCEALMENTTYPE ectype; + OMX_INIT_STRUCT(ectype); + ectype.bStartWithValidFrame = startWithValidFrame ? OMX_TRUE : OMX_FALSE; + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), + OMX_IndexParamBrcmVideoDecodeErrorConcealment, &ectype) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decode error concealment failed\n"); } void cOmx::FlushAudio(void) @@ -618,8 +655,6 @@ void cOmx::FlushAudio(void) if (OMX_SendCommand(ILC_GET_HANDLE(m_comp[eAudioRender]), OMX_CommandFlush, 100, NULL) != OMX_ErrorNone) esyslog("rpihddevice: failed to flush audio render!"); - - m_setAudioStartTime = true; } void cOmx::FlushVideo(bool flushRender) @@ -634,12 +669,16 @@ void cOmx::FlushVideo(bool flushRender) if (flushRender) ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1); - m_setVideoStartTime = true; m_setVideoDiscontinuity = true; } -int cOmx::SetVideoCodec(cVideoCodec::eCodec codec) +int cOmx::SetVideoCodec(cVideoCodec::eCodec codec, eDataUnitType dataUnit) { + dsyslog("rpihddevice: SetVideoCodec()"); + + if (ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle) != 0) + esyslog("rpihddevice: failed to set video decoder to idle state!"); + // configure video decoder OMX_VIDEO_PARAM_PORTFORMATTYPE videoFormat; OMX_INIT_STRUCT(videoFormat); @@ -653,6 +692,11 @@ int cOmx::SetVideoCodec(cVideoCodec::eCodec codec) OMX_IndexParamVideoPortFormat, &videoFormat) != OMX_ErrorNone) esyslog("rpihddevice: failed to set video decoder parameters!"); + // start with valid frames only if codec is MPEG2 + SetVideoErrorConcealment(codec == cVideoCodec::eMPEG2); + SetVideoDataUnitType(dataUnit); + //SetVideoDecoderExtraBuffers(3); + if (ilclient_enable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL) != 0) esyslog("rpihddevice: failed to enable port buffer on video decoder!"); @@ -666,8 +710,19 @@ int cOmx::SetVideoCodec(cVideoCodec::eCodec codec) return 0; } -int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels, int samplingRate, - cAudioPort::ePort audioPort) +void cOmx::SetVideoDecoderExtraBuffers(int extraBuffers) +{ + OMX_PARAM_U32TYPE u32; + OMX_INIT_STRUCT(u32); + u32.nPortIndex = 130; + u32.nU32 = extraBuffers; + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), + OMX_IndexParamBrcmExtraBuffers, &u32) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder extra buffers!"); +} + +int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels, + cAudioPort::ePort audioPort, int samplingRate) { // put audio render onto idle ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); @@ -691,7 +746,7 @@ int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels, int s outputFormat == cAudioCodec::eAC3 ? OMX_AUDIO_CodingDDP : outputFormat == cAudioCodec::eEAC3 ? OMX_AUDIO_CodingDDP : outputFormat == cAudioCodec::eAAC ? OMX_AUDIO_CodingAAC : - outputFormat == cAudioCodec::eDTS ? OMX_AUDIO_CodingDTS : + outputFormat == cAudioCodec::eADTS ? OMX_AUDIO_CodingDTS : OMX_AUDIO_CodingAutoDetect; if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), @@ -741,7 +796,7 @@ int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels, int s esyslog("rpihddevice: failed to set audio render aac parameters!"); break; - case cAudioCodec::eDTS: + case cAudioCodec::eADTS: OMX_AUDIO_PARAM_DTSTYPE dts; OMX_INIT_STRUCT(aac); dts.nPortIndex = 100; @@ -812,8 +867,8 @@ OMX_BUFFERHEADERTYPE* cOmx::GetAudioBuffer(uint64_t pts) if (buf) { cOmx::PtsToTicks(pts, buf->nTimeStamp); - buf->nFlags = m_setAudioStartTime ? OMX_BUFFERFLAG_STARTTIME : 0; - buf->nFlags |= pts ? 0 : OMX_BUFFERFLAG_TIME_UNKNOWN; + buf->nFlags = pts ? 0 : OMX_BUFFERFLAG_TIME_UNKNOWN; + buf->nFlags |= m_setAudioStartTime ? OMX_BUFFERFLAG_STARTTIME : 0; m_setAudioStartTime = false; m_freeAudioBuffers--; @@ -834,8 +889,8 @@ OMX_BUFFERHEADERTYPE* cOmx::GetVideoBuffer(uint64_t pts) if (buf) { cOmx::PtsToTicks(pts, buf->nTimeStamp); - buf->nFlags = m_setVideoStartTime ? - OMX_BUFFERFLAG_STARTTIME : OMX_BUFFERFLAG_TIME_UNKNOWN; + buf->nFlags = pts ? 0 : OMX_BUFFERFLAG_TIME_UNKNOWN; + buf->nFlags |= m_setVideoStartTime ? OMX_BUFFERFLAG_STARTTIME : 0; buf->nFlags |= m_setVideoDiscontinuity ? OMX_BUFFERFLAG_DISCONTINUITY : 0; m_setVideoStartTime = false; @@ -44,7 +44,8 @@ public: void SetClockState(eClockState clockState); void SetClockScale(float scale); - void SetMediaTime(uint64_t pts); + void SetStartTime(uint64_t pts); + void SetCurrentReferenceTime(uint64_t pts); unsigned int GetMediaTime(void); enum eClockReference { @@ -55,14 +56,25 @@ public: void SetClockReference(eClockReference clockReference); void SetVolume(int vol); void SendEos(void); - void Stop(void); + void StopVideo(void); + void StopAudio(void); + + enum eDataUnitType { + eCodedPicture, + eArbitraryStreamSection + }; + + void SetVideoDataUnitType(eDataUnitType dataUnitType); + void SetVideoErrorConcealment(bool startWithValidFrame); + void SetVideoDecoderExtraBuffers(int extraBuffers); void FlushAudio(void); void FlushVideo(bool flushRender = false); - int SetVideoCodec(cVideoCodec::eCodec codec); + int SetVideoCodec(cVideoCodec::eCodec codec, + eDataUnitType dataUnit = eArbitraryStreamSection); int SetupAudioRender(cAudioCodec::eCodec outputFormat, - int channels, int samplingRate, cAudioPort::ePort audioPort); + int channels, cAudioPort::ePort audioPort, int samplingRate = 0); OMX_BUFFERHEADERTYPE* GetAudioBuffer(uint64_t pts = 0); OMX_BUFFERHEADERTYPE* GetVideoBuffer(uint64_t pts = 0); @@ -99,8 +111,8 @@ private: cMutex *m_mutex; - bool m_setVideoStartTime; bool m_setAudioStartTime; + bool m_setVideoStartTime; bool m_setVideoDiscontinuity; int m_freeAudioBuffers; @@ -113,9 +125,9 @@ private: void HandleBufferEmpty(COMPONENT_T *comp); static void OnBufferEmpty(void *instance, COMPONENT_T *comp); - static void OnPortSettingsChanged(void *instance, COMPONENT_T *comp, unsigned int data); - static void OnEndOfStream(void *instance, COMPONENT_T *comp, unsigned int data); - static void OnError(void *instance, COMPONENT_T *comp, unsigned int data); + static void OnPortSettingsChanged(void *instance, COMPONENT_T *comp, OMX_U32 data); + static void OnEndOfStream(void *instance, COMPONENT_T *comp, OMX_U32 data); + static void OnError(void *instance, COMPONENT_T *comp, OMX_U32 data); }; diff --git a/omxdevice.c b/omxdevice.c index d1893e5..24da8bd 100644 --- a/omxdevice.c +++ b/omxdevice.c @@ -11,6 +11,7 @@ #include <vdr/remux.h> #include <vdr/tools.h> +#include <vdr/skins.h> #include <string.h> @@ -20,10 +21,15 @@ cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) : m_omx(new cOmx()), m_audio(new cAudioDecoder(m_omx)), m_mutex(new cMutex()), - m_state(eNone), - m_audioId(0) -{ -} + m_videoCodec(cVideoCodec::eInvalid), + m_hasVideo(false), + m_hasAudio(false), + m_skipAudio(false), + m_playDirection(0), + m_trickRequest(0), + m_audioPts(0), + m_videoPts(0) +{ } cOmxDevice::~cOmxDevice() { @@ -71,14 +77,12 @@ bool cOmxDevice::CanReplay(void) const { dsyslog("rpihddevice: CanReplay"); // video codec de-initialization done - return (m_state == eNone); + return true; //(m_state == eNone); } bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) { - // in case we were in some trick mode - m_omx->SetClockScale(1.0f); - + m_mutex->Lock(); dsyslog("rpihddevice: SetPlayMode(%s)", PlayMode == pmNone ? "none" : PlayMode == pmAudioVideo ? "Audio/Video" : @@ -86,20 +90,19 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) PlayMode == pmAudioOnlyBlack ? "Audio only, black" : PlayMode == pmVideoOnly ? "Video only" : "unsupported"); + + // Stop audio / video if play mode is set to pmNone. Start + // is triggered once a packet is going to be played, since + // we don't know what kind of stream we'll get (audio-only, + // video-only or both) after SetPlayMode() - VDR will always + // pass pmAudioVideo as argument. + switch (PlayMode) { case pmNone: - m_mutex->Lock(); - if (HasVideo()) - m_omx->FlushVideo(true); - if (HasAudio()) - { - m_audio->Reset(); - m_omx->FlushAudio(); - } - m_omx->Stop(); - SetState(eNone); - m_mutex->Unlock(); + ResetAudioVideo(true); + m_omx->StopVideo(); + m_videoCodec = cVideoCodec::eInvalid; break; case pmAudioVideo: @@ -107,95 +110,143 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) case pmAudioOnlyBlack: case pmVideoOnly: break; + + default: + break; } + m_mutex->Unlock(); return true; } +void cOmxDevice::StillPicture(const uchar *Data, int Length) +{ + if (Data[0] == 0x47) + cDevice::StillPicture(Data, Length); + else + { + // to get a picture displayed, PlayVideo() needs to be called + // twice for MPEG2 and 6x for H264... ? + for (int i = 0; i < (m_videoCodec == cVideoCodec::eMPEG2 ? 2 : 6); i++) + PlayVideo(Data, Length, true); + } +} + int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id) { + if (m_skipAudio) + return Length; + m_mutex->Lock(); - int ret = Length; + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - // if first packet is audio, we assume audio only - if (State() == eNone) - SetState(eAudioOnly); + if (!m_hasAudio) + { + m_omx->SetClockReference(cOmx::eClockRefAudio); + m_hasAudio = true; + } - if (State() == eAudioOnly || State() == eAudioVideo) + // keep track of direction in case of trick speed + if (m_trickRequest && pts) { - if (m_audio->Poll()) - { - if (m_audioId != Id) - { - m_audioId = Id; - m_audio->Reset(); - } + if (m_audioPts) + PtsTracker(PtsDiff(m_audioPts, pts)); - //dsyslog("A %llu", PesGetPts(Data)); - if (!m_audio->WriteData(Data + PesPayloadOffset(Data), - PesLength(Data) - PesPayloadOffset(Data), - PesHasPts(Data) ? PesGetPts(Data) : 0)) - esyslog("rpihddevice: failed to write data to audio decoder!"); - } - else - ret = 0; + m_audioPts = pts; } + int ret = m_audio->WriteData(Data + PesPayloadOffset(Data), + Length - PesPayloadOffset(Data), pts); + m_mutex->Unlock(); return ret; } -int cOmxDevice::PlayVideo(const uchar *Data, int Length) +int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool singleFrame) { m_mutex->Lock(); int ret = Length; - if (State() == eNone) - SetState(eStartingVideo); + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; + cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length); + + // video restart after Clear() with same codec + bool videoRestart = (!m_hasVideo && codec == m_videoCodec && + cRpiSetup::IsVideoCodecSupported(codec)); - if (State() == eStartingVideo) + // video restart after SetPlayMode() or codec changed + if (codec != cVideoCodec::eInvalid && codec != m_videoCodec) { - cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length); - if (codec != cVideoCodec::eInvalid) + m_videoCodec = codec; + + if (m_hasVideo) { - if (cRpiSetup::IsVideoCodecSupported(codec)) - { - m_omx->SetVideoCodec(codec); - SetState(eAudioVideo); - dsyslog("rpihddevice: set video codec to %s", - cVideoCodec::Str(codec)); - } - else - { - SetState(eAudioOnly); - esyslog("rpihddevice: %s video codec not supported!", - cVideoCodec::Str(codec)); - } + m_omx->StopVideo(); + m_hasVideo = false; + } + + if (cRpiSetup::IsVideoCodecSupported(codec)) + { + videoRestart = true; + m_omx->SetVideoCodec(codec, cOmx::eArbitraryStreamSection); + dsyslog("rpihddevice: set video codec to %s", + cVideoCodec::Str(codec)); + } + else + Skins.QueueMessage(mtError, tr("video format not supported!")); + } + + if (videoRestart) + { + m_hasVideo = true; + + if (!m_hasAudio) + { + m_omx->SetClockReference(cOmx::eClockRefVideo); + m_omx->SetCurrentReferenceTime(0); + m_omx->SetClockState(cOmx::eClockStateWaitForVideo); } } - if (State() == eVideoOnly || State() == eAudioVideo) + // keep track of direction in case of trick speed + if (m_trickRequest && pts) { - int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); - if (buf) + if (m_videoPts) + PtsTracker(PtsDiff(m_videoPts, pts)); + + m_videoPts = pts; + } + + if (m_hasVideo) + { + while (Length) { - //dsyslog("V %llu", PesGetPts(Data)); const uchar *payload = Data + PesPayloadOffset(Data); - int length = PesLength(Data) - PesPayloadOffset(Data); - if (length <= buf->nAllocLen) + unsigned int length = PesLength(Data) - PesPayloadOffset(Data); + + OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); + if (buf) { - memcpy(buf->pBuffer, payload, length); buf->nFilledLen = length; + memcpy(buf->pBuffer, payload, length); + + if (singleFrame && Length == PesLength(Data)) + buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + + if (!m_omx->EmptyVideoBuffer(buf)) + { + ret = 0; + esyslog("rpihddevice: failed to pass buffer to video decoder!"); + break; + } } else - esyslog("rpihddevice: video packet too long for video buffer!"); - - if (!m_omx->EmptyVideoBuffer(buf)) { ret = 0; - esyslog("rpihddevice: failed to pass buffer to video decoder!"); + break; } + Length -= PesLength(Data); + Data += PesLength(Data); } } @@ -203,85 +254,133 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length) return ret; } -void cOmxDevice::SetState(eState state) -{ - switch (state) - { - case eNone: - m_omx->SetClockState(cOmx::eClockStateStop); - break; - - case eStartingVideo: - break; - - case eAudioOnly: - m_omx->SetClockState(cOmx::eClockStateWaitForAudio); - break; - - case eVideoOnly: - m_omx->SetClockState(cOmx::eClockStateWaitForVideo); - break; - - case eAudioVideo: - m_omx->SetClockState(cOmx::eClockStateWaitForAudioVideo); - break; - } - m_state = state; -} - int64_t cOmxDevice::GetSTC(void) { - //dsyslog("S %llu", m_omx->GetSTC()); return m_omx->GetSTC(); } void cOmxDevice::Play(void) { dsyslog("rpihddevice: Play()"); + + Clear(); + return; + + m_mutex->Lock(); + + m_skipAudio = false; m_omx->SetClockScale(1.0f); + m_omx->SetClockReference(m_hasAudio ? + cOmx::eClockRefAudio : cOmx::eClockRefVideo); + + m_mutex->Unlock(); cDevice::Play(); } void cOmxDevice::Freeze(void) { - dsyslog("rpihddevice: Freeze()"); + m_mutex->Lock(); m_omx->SetClockScale(0.0f); + m_mutex->Unlock(); cDevice::Freeze(); } void cOmxDevice::TrickSpeed(int Speed) { - dsyslog("rpihddevice: TrickSpeed(%d)", Speed); + m_mutex->Lock(); + m_audioPts = 0; + m_videoPts = 0; + m_playDirection = 0; + + // play direction is ambiguous for fast modes, start PTS tracking + if (Speed == 1 || Speed == 3 || Speed == 6) + m_trickRequest = Speed; + else + ApplyTrickSpeed(Speed); + + m_mutex->Unlock(); +} + +void cOmxDevice::ApplyTrickSpeed(int trickSpeed, bool reverse) +{ + float scale = + // slow forward + trickSpeed == 8 ? 0.125f : + trickSpeed == 4 ? 0.25f : + trickSpeed == 2 ? 0.5f : + + // fast for-/backward + trickSpeed == 6 ? (reverse ? -2.0f : 2.0f) : + trickSpeed == 3 ? (reverse ? -4.0f : 4.0f) : + trickSpeed == 1 ? (reverse ? -12.0f : 12.0f) : + + // slow backward + trickSpeed == 63 ? -0.125f : + trickSpeed == 48 ? -0.25f : + trickSpeed == 24 ? -0.5f : 1.0f; + + m_omx->SetClockScale(scale); + m_omx->SetClockReference(cOmx::eClockRefVideo); + + m_trickRequest = 0; + m_skipAudio = true; + + dsyslog("rpihddevice: ApplyTrickSpeed(%.3f, %sward)", + scale, reverse ? "back" : "for"); + +} + +void cOmxDevice::PtsTracker(int64_t ptsDiff) +{ + if (ptsDiff < 0) + --m_playDirection; + else if (ptsDiff > 0) + m_playDirection += 2; + + if (m_playDirection < -2 || m_playDirection > 3) + ApplyTrickSpeed(m_trickRequest, m_playDirection < 0); } bool cOmxDevice::Flush(int TimeoutMs) { dsyslog("rpihddevice: Flush()"); - return true; } void cOmxDevice::Clear(void) { - m_mutex->Lock(); dsyslog("rpihddevice: Clear()"); + m_mutex->Lock(); + ResetAudioVideo(); + m_omx->SetStartTime(0); + + m_mutex->Unlock(); + cDevice::Clear(); +} + +void cOmxDevice::ResetAudioVideo(bool flushVideoRender) +{ m_omx->SetClockScale(1.0f); - m_omx->SetMediaTime(0); + m_skipAudio = false; + m_trickRequest = 0; + m_audioPts = 0; + m_videoPts = 0; - if (HasAudio()) + if (m_hasAudio) { m_audio->Reset(); m_omx->FlushAudio(); } - if (HasVideo()) - m_omx->FlushVideo(false); + if (m_hasVideo) + m_omx->FlushVideo(flushVideoRender); - m_mutex->Unlock(); - cDevice::Clear(); + m_hasAudio = false; + m_hasVideo = false; } + void cOmxDevice::SetVolumeDevice(int Volume) { m_omx->SetVolume(Volume); @@ -293,7 +392,7 @@ bool cOmxDevice::Poll(cPoller &Poller, int TimeoutMs) time.Set(); while (!m_omx->PollVideoBuffers() || !m_audio->Poll()) { - if (time.Elapsed() >= TimeoutMs) + if (time.Elapsed() >= (unsigned)TimeoutMs) return false; cCondWait::SleepMs(5); } @@ -316,6 +415,7 @@ cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length) return cVideoCodec::eInvalid; const uchar *p = data + PesPayloadOffset(data); + for (int i = 0; i < 5; i++) { // find start code prefix - should be right at the beginning of payload @@ -324,17 +424,23 @@ cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length) if (p[i + 3] == 0xb3) // sequence header return cVideoCodec::eMPEG2; + //p[i + 3] = 0xf0 else if (p[i + 3] == 0x09) // slice { + // quick hack for converted mkvs + if (p[i + 4] == 0xf0) + return cVideoCodec::eH264; + switch (p[i + 4] >> 5) { case 0: case 3: case 5: // I frame return cVideoCodec::eH264; - case 1: case 4: case 6: // P frame - case 2: case 7: // B frame + case 2: case 7: // B frame + case 1: case 4: case 6: // P frame default: - return cVideoCodec::eInvalid; +// return cVideoCodec::eInvalid; + return cVideoCodec::eH264; } } return cVideoCodec::eInvalid; diff --git a/omxdevice.h b/omxdevice.h index 52a94a3..10efea4 100644 --- a/omxdevice.h +++ b/omxdevice.h @@ -34,21 +34,24 @@ public: virtual int Init(void); virtual int DeInit(void); - virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect); - virtual bool HasDecoder(void) const { return true; }; + virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect); + virtual bool SetPlayMode(ePlayMode PlayMode); virtual bool CanReplay(void) const; - virtual int PlayVideo(const uchar *Data, int Length); + virtual void StillPicture(const uchar *Data, int Length); + virtual int PlayAudio(const uchar *Data, int Length, uchar Id); + virtual int PlayVideo(const uchar *Data, int Length) + { return PlayVideo(Data, Length, false); } - virtual int64_t GetSTC(void); + virtual int PlayVideo(const uchar *Data, int Length, bool singleFrame = false); - virtual bool Flush(int TimeoutMs = 0); + virtual int64_t GetSTC(void); - virtual bool HasIBPTrickSpeed(void) { return false; } + virtual bool HasIBPTrickSpeed(void) { return true; } virtual void TrickSpeed(int Speed); virtual void Clear(void); virtual void Play(void); @@ -56,6 +59,7 @@ public: virtual void SetVolumeDevice(int Volume); + virtual bool Flush(int TimeoutMs = 0); virtual bool Poll(cPoller &Poller, int TimeoutMs = 0); protected: @@ -65,28 +69,28 @@ protected: private: void (*m_onPrimaryDevice)(void); - virtual cVideoCodec::eCodec ParseVideoCodec(const uchar *data, int length); - virtual void SetState(eState state); - virtual inline eState State() { return m_state; } - inline bool HasVideo() { - return m_state == eStartingVideo || - m_state == eVideoOnly || - m_state == eAudioVideo; - }; + void ResetAudioVideo(bool flushVideoRender = false); - inline bool HasAudio() { - return m_state == eAudioOnly || - m_state == eAudioVideo; - }; + void ApplyTrickSpeed(int trickSpeed, bool reverse = false); + void PtsTracker(int64_t ptsDiff); cOmx *m_omx; cAudioDecoder *m_audio; cMutex *m_mutex; - eState m_state; - uchar m_audioId; + cVideoCodec::eCodec m_videoCodec; + + bool m_hasVideo; + bool m_hasAudio; + + bool m_skipAudio; + int m_playDirection; + int m_trickRequest; + + int64_t m_audioPts; + int64_t m_videoPts; }; #endif @@ -24,8 +24,8 @@ public: m_mutex(0), m_width(0), m_height(0), - m_pixmap(0), m_aspect(0), + m_pixmap(0), m_d(0), m_x(0), m_y(0), @@ -270,9 +270,6 @@ void cOvgOsd::Flush(void) LOCK_PIXMAPS; while (cPixmapMemory *pm = RenderPixmaps()) { - int w = pm->ViewPort().Width(); - int h = pm->ViewPort().Height(); - int d = w * sizeof(tColor); m_ovg->DrawPixmap( Left() + pm->ViewPort().X(), Top() + pm->ViewPort().Y(), pm->ViewPort().Width(), pm->ViewPort().Height(), diff --git a/po/de_DE.po b/po/de_DE.po index 7d6bc20..6976645 100644 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -7,16 +7,19 @@ msgid "" msgstr "" "Project-Id-Version: vdr-rpihddevice 0.0.4\n" "Report-Msgid-Bugs-To: <see README>\n" -"POT-Creation-Date: 2013-10-14 13:33+0200\n" +"POT-Creation-Date: 2013-12-08 12:29+0100\n" "PO-Revision-Date: 2013-10-14 13:36+0200\n" "Last-Translator: <thomas@reufer.ch>\n" "Language-Team: German <translation-team-de@lists.sourceforge.net>\n" "Language: de\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=ASCII\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "video format not supported!" +msgstr "Videoformat nicht unterstützt!" + msgid "analog" msgstr "analog" diff --git a/rpihddevice.c b/rpihddevice.c index fa21315..68c78bb 100644 --- a/rpihddevice.c +++ b/rpihddevice.c @@ -12,7 +12,7 @@ #include "setup.h" #include "types.h" -static const char *VERSION = "0.0.5"; +static const char *VERSION = "0.0.6"; static const char *DESCRIPTION = "HD output device for Raspberry Pi"; class cDummyDevice : cDevice @@ -82,12 +82,12 @@ bool cPluginRpiHdDevice::Initialize(void) // test whether MPEG2 license is available if (!cRpiSetup::IsVideoCodecSupported(cVideoCodec::eMPEG2)) - esyslog("rpihddevice: WARNING: MPEG2 video decoder not enabled!"); + dsyslog("rpihddevice: MPEG2 video decoder not enabled!"); m_device = new cOmxDevice(&OnPrimaryDevice); if (m_device) - return (m_device->Init() == 0); + return !m_device->Init(); return false; } @@ -115,7 +115,7 @@ bool cRpiSetup::IsAudioFormatSupported(cAudioCodec::eCodec codec, { // AAC and DTS pass-through currently not supported if (codec == cAudioCodec::eAAC || - codec == cAudioCodec::eDTS) + codec == cAudioCodec::eADTS) return false; if (vc_tv_hdmi_audio_supported( @@ -123,7 +123,6 @@ bool cRpiSetup::IsAudioFormatSupported(cAudioCodec::eCodec codec, codec == cAudioCodec::eAC3 ? EDID_AudioFormat_eAC3 : codec == cAudioCodec::eEAC3 ? EDID_AudioFormat_eEAC3 : codec == cAudioCodec::eAAC ? EDID_AudioFormat_eAAC : - codec == cAudioCodec::eDTS ? EDID_AudioFormat_eDTS : EDID_AudioFormat_ePCM, channels, samplingRate == 32000 ? EDID_AudioSampleRate_e32KHz : samplingRate == 44000 ? EDID_AudioSampleRate_e44KHz : @@ -17,7 +17,7 @@ public: eAC3, eEAC3, eAAC, - eDTS, + eADTS, eNumCodecs, eInvalid }; @@ -28,7 +28,7 @@ public: (codec == eAC3) ? "AC3" : (codec == eEAC3) ? "E-AC3" : (codec == eAAC) ? "AAC" : - (codec == eDTS) ? "DTS" : "unknown"; + (codec == eADTS) ? "ADTS" : "unknown"; } }; |