diff options
author | Thomas Reufer <thomas@reufer.ch> | 2014-01-07 16:32:58 +0100 |
---|---|---|
committer | Thomas Reufer <thomas@reufer.ch> | 2014-01-07 16:32:58 +0100 |
commit | dd9fbf38610934623869728d1b00904d15783480 (patch) | |
tree | 789059da0b20f3c7d9711b3dd638a770d2a6cfb2 | |
parent | 9512123c95324f1679d748993662bd9f08f6f763 (diff) | |
download | vdr-plugin-rpihddevice-0.0.5.tar.gz vdr-plugin-rpihddevice-0.0.5.tar.bz2 |
2013-11-17: Version 0.0.50.0.5
-------------------------
- new:
- improved audio format detection (taken from softhddevice)
- separate thread for audio decoding
- fixed:
- jump forward/backward in recordings
- several minor bugfixes
- missing:
- still picture
- trick modes
- deinterlacer
- video format/output options
-rw-r--r-- | HISTORY | 18 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | audio.c | 779 | ||||
-rw-r--r-- | audio.h | 82 | ||||
-rw-r--r-- | omx.c | 876 | ||||
-rw-r--r-- | omx.h | 122 | ||||
-rw-r--r-- | omxdevice.c | 1149 | ||||
-rw-r--r-- | omxdevice.h | 41 | ||||
-rw-r--r-- | ovgosd.c | 1 | ||||
-rw-r--r-- | rpihddevice.c | 8 | ||||
-rw-r--r-- | setup.c | 27 | ||||
-rw-r--r-- | setup.h | 26 | ||||
-rw-r--r-- | types.h | 67 |
14 files changed, 2001 insertions, 1203 deletions
@@ -1,6 +1,20 @@ VDR Plugin 'rpihddevice' Revision History ----------------------------------------- +2013-11-17: Version 0.0.5 +------------------------- +- new: + - improved audio format detection (taken from softhddevice) + - separate thread for audio decoding +- fixed: + - jump forward/backward in recordings + - several minor bugfixes +- missing: + - still picture + - trick modes + - deinterlacer + - video format/output options + 2013-10-14: Version 0.0.4 ------------------------- - new: @@ -11,7 +25,7 @@ VDR Plugin 'rpihddevice' Revision History - removed drawing of black box in front of console which lead to malfunction due to memory bandwidth problem. console blank out will be handled with video format/output options in future versions. -- missing +- missing: - trick modes - deinterlacer - video format/output options @@ -26,7 +40,7 @@ VDR Plugin 'rpihddevice' Revision History - replay start/stop/pause - improved H264 detection - blank out console -- missing +- missing: - trick modes - other audio formats - much more... @@ -54,8 +54,8 @@ DEFINES += -Wno-write-strings -fpermissive CXXFLAGS += -D__STDC_CONSTANT_MACROS ILCDIR =ilclient -VCINCDIR =/opt/vc/include -VCLIBDIR =/opt/vc/lib +VCINCDIR =$(SDKSTAGE)/opt/vc/include +VCLIBDIR =$(SDKSTAGE)/opt/vc/lib INCLUDES += -I$(ILCDIR) -I$(VCINCDIR) -I$(VCINCDIR)/interface/vcos/pthreads -I$(VCINCDIR)/interface/vmcs_host/linux @@ -65,7 +65,7 @@ LDFLAGS += -Wl,--whole-archive $(ILCDIR)/libilclient.a -Wl,--no-whole-archive ### The object files (add further files here): ILCLIENT = $(ILCDIR)/libilclient.a -OBJS = $(PLUGIN).o setup.o audio.o omxdevice.o ovgosd.o +OBJS = $(PLUGIN).o setup.o omx.o audio.o omxdevice.o ovgosd.o ### The main target: @@ -15,4 +15,4 @@ Description: Requirements: - valid MPEG2 licence - - libmpg123 + - ffmpeg (tested with 1.0.7) @@ -6,55 +6,507 @@ #include "audio.h" #include "setup.h" +#include "omx.h" #include <vdr/tools.h> #include <vdr/remux.h> #include <string.h> -cAudioDecoder::cAudioDecoder() : - m_codec(ePCM), - m_outputFormat(ePCM), - m_outputPort(eLocal), +class cAudioParser +{ + +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; } + + int Init(void) + { + return av_new_packet(&m_packet, 64 * 1024 /* 1 PES packet */); + } + + int DeInit(void) + { + av_free_packet(&m_packet); + return 0; + } + + void Reset(void) + { + m_packet.stream_index = 0; + 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) + 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); + return true; + } + + void Shrink(unsigned int length) + { + if (length < m_packet.stream_index) + { + 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); + } + else + Reset(); + } + + cAudioCodec::eCodec Parse(unsigned int &offset) + { + cAudioCodec::eCodec codec = cAudioCodec::eInvalid; + + while (Size() - offset >= 5) + { + const uint8_t *p = Data() + offset; + int n = Size() - offset; + int r = 0; + + // 4 bytes 0xFFExxxxx MPEG audio + // 3 bytes 0x56Exxx AAC LATM audio + // 5 bytes 0x0B77xxxxxx AC-3 audio + // 6 bytes 0x0B77xxxxxxxx E-AC-3 audio + // 7/9 bytes 0xFFFxxxxxxxxxxx ADTS audio + // PCM audio can't be found + + if (FastMpegCheck(p)) + { + r = MpegCheck(p, n); + codec = cAudioCodec::eMPG; + } + else if (FastAc3Check(p)) + { + r = Ac3Check(p, n); + codec = cAudioCodec::eAC3; + + if (r > 0 && p[5] > (10 << 3)) + codec = cAudioCodec::eEAC3; + } + else if (FastLatmCheck(p)) + { + r = LatmCheck(p, n); + codec = cAudioCodec::eAAC; + } + else if (FastAdtsCheck(p)) + { + r = AdtsCheck(p, n); + codec = cAudioCodec::eDTS; + } + + if (r < 0) // need more bytes + break; + + if (r > 0) + return codec; + + ++offset; + } + return cAudioCodec::eInvalid; + } + +private: + + AVPacket m_packet; + + /* ------------------------------------------------------------------------- */ + /* 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 Ac3FrameSizeTable[38][3]; + + /// + /// Fast check for MPEG audio. + /// + /// 4 bytes 0xFFExxxxx MPEG audio + /// + static bool FastMpegCheck(const uint8_t *p) + { + if (p[0] != 0xFF) // 11bit frame sync + return false; + if ((p[1] & 0xE0) != 0xE0) + return false; + if ((p[1] & 0x18) == 0x08) // version ID - 01 reserved + return false; + if (!(p[1] & 0x06)) // layer description - 00 reserved + return false; + if ((p[2] & 0xF0) == 0xF0) // bit rate index - 1111 reserved + return false; + if ((p[2] & 0x0C) == 0x0C) // sampling rate index - 11 reserved + return false; + return true; + } + + /// 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 Padding 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 frame_size; + 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]; + if (!sample_rate) + return 0; + + 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; + + switch (layer) + { + case 1: + frame_size = (12000 * bit_rate) / sample_rate; + frame_size = (frame_size + padding) * 4; + break; + case 2: + case 3: + default: + frame_size = (144000 * bit_rate) / sample_rate; + 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; + + return 0; + } + + /// + /// Fast check for (E-)AC-3 audio. + /// + /// 5 bytes 0x0B77xxxxxx AC-3 audio + /// + static bool FastAc3Check(const uint8_t *p) + { + if (p[0] != 0x0B) // 16bit sync + return false; + if (p[1] != 0x77) + return false; + return true; + } + + /// + /// Check for (E-)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 + /// + /// o AC-3 Header + /// AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF + /// + /// o a 16x Frame sync, always 0x0B77 + /// o b 16x CRC 16 + /// o c 2x Sample rate + /// o d 6x Frame size code + /// o e 5x Bit stream ID + /// o f 3x Bit stream mode + /// + /// o E-AC-3 Header + /// AAAAAAAA AAAAAAAA BBCCCDDD DDDDDDDD EEFFGGGH IIIII... + /// + /// o a 16x Frame sync, always 0x0B77 + /// o b 2x Frame type + /// o c 3x Sub stream ID + /// o d 10x Frame size - 1 in words + /// o e 2x Frame size code + /// o f 2x Frame size code 2 + /// + static int Ac3Check(const uint8_t *p, int size) + { + int frame_size; + + if (size < 5) // need 5 bytes to see if AC-3/E-AC-3 + return -5; + + if (p[5] > (10 << 3)) // E-AC-3 + { + if ((p[4] & 0xF0) == 0xF0) // invalid fscod fscod2 + return 0; + + frame_size = ((p[2] & 0x03) << 8) + p[3] + 1; + frame_size *= 2; + } + else // AC-3 + { + int fscod = p[4] >> 6; + if (fscod == 0x03) // invalid sample rate + return 0; + + int frmsizcod = p[4] & 0x3F; + if (frmsizcod > 37) // invalid frame size + return 0; + + // 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; + + return 0; + } + + /// + /// Fast check for AAC LATM audio. + /// + /// 3 bytes 0x56Exxx AAC LATM audio + /// + static bool FastLatmCheck(const uint8_t *p) + { + if (p[0] != 0x56) // 11bit sync + return false; + if ((p[1] & 0xE0) != 0xE0) + return false; + return true; + } + + /// + /// 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 *p, int size) + { + // 13 bit frame size without header + int frame_size = ((p[1] & 0x1F) << 8) + p[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(p + frame_size)) + return frame_size; + + return 0; + } + + /// + /// Fast check for ADTS Audio Data Transport Stream. + /// + /// 7/9 bytes 0xFFFxxxxxxxxxxx(xxxx) ADTS audio + /// + static bool FastAdtsCheck(const uint8_t *p) + { + if (p[0] != 0xFF) // 12bit sync + return false; + if ((p[1] & 0xF6) != 0xF0) // sync + layer must be 0 + return false; + if ((p[2] & 0x3C) == 0x3C) // sampling frequency index != 15 + return false; + return true; + } + + /// + /// Check for ADTS Audio Data Transport Stream. + /// + /// 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) + /// + /// o A*12 sync word 0xFFF + /// o B*1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 + /// o C*2 layer: always 0 + /// o .. + /// o F*4 sampling frequency index (15 is invalid) + /// o .. + /// o M*13 frame length + /// + static int AdtsCheck(const uint8_t *p, int size) + { + if (size < 6) + return -6; + + int 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; + + // check if after this frame a new ADTS frame starts + if (FastAdtsCheck(p + frame_size)) + return frame_size; + + return 0; + } +}; + +/// +/// MPEG bit rate table. +/// +/// BitRateTable[Version][Layer][Index] +/// +const uint16_t cAudioParser::BitRateTable[2][3][16] = +{ + { // MPEG Version 1 + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} + }, + { // MPEG Version 2 & 2.5 + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} + } +}; + +/// +/// MPEG sample rate table. +/// +const uint16_t cAudioParser::SampleRateTable[4] = { 44100, 48000, 32000, 0 }; + +/// +/// Possible AC-3 frame sizes. +/// +/// from ATSC A/52 table 5.18 frame size code table. +/// +const uint16_t cAudioParser::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}, + { 128, 139, 192}, { 128, 140, 192}, { 160, 174, 240}, {160, 175, 240}, + { 192, 208, 288}, { 192, 209, 288}, { 224, 243, 336}, {224, 244, 336}, + { 256, 278, 384}, { 256, 279, 384}, { 320, 348, 480}, {320, 349, 480}, + { 384, 417, 576}, { 384, 418, 576}, { 448, 487, 672}, {448, 488, 672}, + { 512, 557, 768}, { 512, 558, 768}, { 640, 696, 960}, {640, 697, 960}, + { 768, 835, 1152}, { 768, 836, 1152}, { 896, 975, 1344}, {896, 976, 1344}, + {1024, 1114, 1536}, {1024, 1115, 1536}, {1152, 1253, 1728}, + {1152, 1254, 1728}, {1280, 1393, 1920}, {1280, 1394, 1920}, +}; + +/* ------------------------------------------------------------------------- */ + +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_pts(0), m_frame(0), - m_mutex(new cMutex()) { } + m_mutex(new cMutex()), + m_newData(new cCondWait()), + m_parser(new cAudioParser()), + m_omx(omx) +{ +} cAudioDecoder::~cAudioDecoder() { + delete m_parser; + delete m_newData; delete m_mutex; } int cAudioDecoder::Init(void) { - int ret = 0; + int ret = m_parser->Init(); + if (ret) + return ret; avcodec_register_all(); - m_codecs[ePCM ].codec = NULL; - m_codecs[eMPG ].codec = avcodec_find_decoder(CODEC_ID_MP3); - m_codecs[eAC3 ].codec = avcodec_find_decoder(CODEC_ID_AC3); - m_codecs[eEAC3].codec = avcodec_find_decoder(CODEC_ID_EAC3); - m_codecs[eAAC ].codec = avcodec_find_decoder(CODEC_ID_AAC_LATM); - m_codecs[eDTS ].codec = avcodec_find_decoder(CODEC_ID_DTS); + m_codecs[cAudioCodec::ePCM ].codec = NULL; + m_codecs[cAudioCodec::eMPG ].codec = avcodec_find_decoder(CODEC_ID_MP3); + 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); - for (int i = 0; i < eNumCodecs; i++) + for (int i = 0; i < cAudioCodec::eNumCodecs; i++) { - eCodec codec = static_cast<eCodec>(i); + cAudioCodec::eCodec codec = static_cast<cAudioCodec::eCodec>(i); if (m_codecs[codec].codec) { m_codecs[codec].context = avcodec_alloc_context3(m_codecs[codec].codec); if (!m_codecs[codec].context) { - esyslog("rpihddevice: failed to allocate %s context!", CodecStr(codec)); + esyslog("rpihddevice: failed to allocate %s context!", cAudioCodec::Str(codec)); ret = -1; break; } if (avcodec_open2(m_codecs[codec].context, m_codecs[codec].codec, NULL) < 0) { - esyslog("rpihddevice: failed to open %s decoder!", CodecStr(codec)); + esyslog("rpihddevice: failed to open %s decoder!", cAudioCodec::Str(codec)); ret = -1; break; } @@ -67,138 +519,168 @@ int cAudioDecoder::Init(void) esyslog("rpihddevice: failed to allocate audio frame!"); ret = -1; } + avcodec_get_frame_defaults(m_frame); if (ret < 0) DeInit(); + Start(); + return ret; } int cAudioDecoder::DeInit(void) { - for (int i = 0; i < eNumCodecs; i++) + Cancel(); + + for (int i = 0; i < cAudioCodec::eNumCodecs; i++) { - eCodec codec = static_cast<eCodec>(i); + cAudioCodec::eCodec codec = static_cast<cAudioCodec::eCodec>(i); avcodec_close(m_codecs[codec].context); av_free(m_codecs[codec].context); } av_free(m_frame); + m_parser->DeInit(); return 0; } -bool cAudioDecoder::SetupAudioCodec(const unsigned char *data, int length) +bool cAudioDecoder::WriteData(const unsigned char *buf, unsigned int length, uint64_t pts) { m_mutex->Lock(); - bool ret = false; + bool ret = m_parser->Append(buf, length); + if (ret) + { + // set current pts as reference + m_pts = pts; - // try to decode audio sample - AVPacket avpkt; - av_init_packet(&avpkt); - avpkt.data = (unsigned char *)(data + PesPayloadOffset(data)); - avpkt.size = PesLength(data) - PesPayloadOffset(data); + ProbeCodec(); + m_newData->Signal(); + } - for (int i = 0; i < eNumCodecs; i++) - { - eCodec codec = static_cast<eCodec>(i); - if (m_codecs[codec].codec) - { - int frame = 0; - avcodec_get_frame_defaults(m_frame); + m_mutex->Unlock(); + return ret; +} - m_codecs[codec].context->request_channel_layout = AV_CH_LAYOUT_NATIVE; - m_codecs[codec].context->request_channels = 0; +void cAudioDecoder::Reset(void) +{ + m_mutex->Lock(); + m_parser->Reset(); + m_codec = cAudioCodec::eInvalid; + avcodec_get_frame_defaults(m_frame); + m_mutex->Unlock(); +} - int len = avcodec_decode_audio4(m_codecs[codec].context, m_frame, &frame, &avpkt); +bool cAudioDecoder::Poll(void) +{ + return !m_parser->Size() && m_omx->PollAudioBuffers(); +} - if (len > 0 && frame) +void cAudioDecoder::Action(void) +{ + dsyslog("rpihddevice: cAudioDecoder() thread started"); + + while (Running()) + { + if (m_parser->Size()) + { + m_mutex->Lock(); + if (m_outputFormatChanged) { - m_codec = codec; - m_channels = m_codecs[m_codec].context->channels; - m_samplingRate = m_codecs[m_codec].context->sample_rate; - dsyslog("rpihddevice: set audio codec to %s with %d channels, %dHz", - CodecStr(m_codec), m_channels, m_samplingRate); - - m_passthrough = false; - m_outputFormat = ePCM; - m_outputPort = eLocal; - - if (cRpiSetup::GetAudioPort() == eHDMI && - cRpiSetup::IsAudioFormatSupported(ePCM, 2, 48000)) - { - m_outputPort = eHDMI; + m_outputFormatChanged = false; + m_omx->SetupAudioRender( + m_outputFormat, m_channels, + m_samplingRate, m_outputPort); + } - if (cRpiSetup::IsAudioPassthrough() && - cRpiSetup::IsAudioFormatSupported(m_codec, m_channels, m_samplingRate)) - { - m_passthrough = true; - m_outputFormat = m_codec; - } - dsyslog("rpihddevice: set HDMI audio output format to %s%s", - CodecStr(m_outputFormat), m_passthrough ? " (pass-through)" : ""); - } - else - { - m_codecs[m_codec].context->request_channel_layout = AV_CH_LAYOUT_STEREO_DOWNMIX; - m_codecs[m_codec].context->request_channels = 2; - m_channels = 2; - dsyslog("rpihddevice: set analog audio output format to PCM stereo"); - } + OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(m_pts); + if (buf) + { + while (DecodeFrame()) + buf->nFilledLen += ReadFrame( + buf->pBuffer + buf->nFilledLen, + buf->nAllocLen - buf->nFilledLen); - ret = true; - break; + if (!m_omx->EmptyAudioBuffer(buf)) + esyslog("rpihddevice: failed to empty audio buffer!"); + } + else + { + esyslog("rpihddevice: failed to get audio buffer!"); + cCondWait::SleepMs(5); } + m_mutex->Unlock(); } + else + m_newData->Wait(50); } - m_mutex->Unlock(); - return ret; + dsyslog("rpihddevice: cAudioDecoder() thread ended"); } -unsigned int cAudioDecoder::DecodeAudio(const unsigned char *data, int length, unsigned char *outbuf, int bufsize) +unsigned int cAudioDecoder::DecodeFrame() { - m_mutex->Lock(); + unsigned int ret = 0; if (m_passthrough) + ret = m_parser->Size(); + + else if (m_parser->Size()) { - if (length > bufsize) - esyslog("rpihddevice: pass-through audio frame is bigger than output buffer!"); - else - memcpy(outbuf, data, length); + int frame = 0; + int len = avcodec_decode_audio4(m_codecs[m_codec].context, + m_frame, &frame, m_parser->Packet()); - return length; + // 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; +} - AVPacket avpkt; - av_init_packet(&avpkt); - - avpkt.data = (unsigned char *)data; - avpkt.size = length; - - unsigned int outsize = 0; +unsigned int cAudioDecoder::ReadFrame(unsigned char *buf, unsigned int bufsize) +{ + unsigned int ret = 0; - while (avpkt.size > 0) + if (m_passthrough) { - int frame = 0; - avcodec_get_frame_defaults(m_frame); - - int len = avcodec_decode_audio4(m_codecs[m_codec].context, m_frame, &frame, &avpkt); - if (len < 0) - break; + // for pass-through directly read from AV packet + if (m_parser->Size() > bufsize) + ret = bufsize; + else + ret = m_parser->Size(); - if (frame) + memcpy(buf, m_parser->Data(), ret); + m_parser->Shrink(ret); + } + else + { + if (m_frame->nb_samples > 0) { - unsigned int framesize = av_samples_get_buffer_size(NULL, + 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 (outsize + framesize <= bufsize) + if (ret > bufsize) + { + esyslog("rpihddevice: decoded audio frame too big!"); + ret = 0; + } + else { 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*)outbuf; + int32_t* dst = (int32_t*)buf; for (int i = 0; i < m_frame->nb_samples; i++) { @@ -209,21 +691,106 @@ unsigned int cAudioDecoder::DecodeAudio(const unsigned char *data, int length, u } } else - memcpy(outbuf, m_frame->data[0], framesize); - - outsize += framesize; - outbuf += framesize; + memcpy(buf, m_frame->data[0], ret); } - else + } + } + 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()); + + if (len > 0 && frame) { - esyslog("rpihddevice: decoded audio frame is bigger than output buffer!"); - break; + SetCodec(codec); + ret = true; } } - avpkt.size -= len; - avpkt.data += len; } + return ret; +} - m_mutex->Unlock(); - return outsize; +void cAudioDecoder::SetCodec(cAudioCodec::eCodec codec) +{ + if (codec != cAudioCodec::eInvalid) + { + if (m_codec == cAudioCodec::eInvalid) + m_outputFormatChanged = true; + + m_codec = codec; + m_codecs[m_codec].context->request_channel_layout = AV_CH_LAYOUT_NATIVE; + m_codecs[m_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)) + { + outputPort = cAudioPort::eHDMI; + + if (cRpiSetup::IsAudioPassthrough() && + cRpiSetup::IsAudioFormatSupported(m_codec, + m_codecs[m_codec].context->channels, + m_codecs[m_codec].context->sample_rate)) + { + m_passthrough = true; + outputFormat = m_codec; + } + } + else + { + m_codecs[m_codec].context->request_channel_layout = AV_CH_LAYOUT_STEREO_DOWNMIX; + m_codecs[m_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)" : ""); + } + } } @@ -12,56 +12,38 @@ extern "C" #include <libavcodec/avcodec.h> } -class cMutex; +#include <vdr/thread.h> -class cAudioDecoder -{ +#include "types.h" +#include "omx.h" -public: +class cAudioParser; - enum eCodec { - ePCM, - eMPG, - eAC3, - eEAC3, - eAAC, - eDTS, - eNumCodecs - }; +class cAudioDecoder : public cThread +{ - enum ePort { - eLocal, - eHDMI - }; +public: - static const char* CodecStr(eCodec codec) - { - return (codec == ePCM) ? "PCM" : - (codec == eMPG) ? "MPG" : - (codec == eAC3) ? "AC3" : - (codec == eEAC3) ? "E-AC3" : - (codec == eAAC) ? "AAC" : - (codec == eDTS) ? "DTS" : "unknown"; - } - - cAudioDecoder(); + cAudioDecoder(cOmx *omx); virtual ~cAudioDecoder(); virtual int Init(void); virtual int DeInit(void); - virtual bool SetupAudioCodec(const unsigned char *data, int length); + virtual bool WriteData(const unsigned char *buf, unsigned int length, uint64_t pts = 0); - virtual eCodec GetCodec(void) { return m_codec; } - virtual eCodec GetOutputFormat(void) { return m_outputFormat; } - virtual ePort GetOutputPort(void) { return m_outputPort; } - virtual int GetChannels(void) { return m_channels; } - virtual int GetSamplingrate(void) { return m_samplingRate; } + virtual bool Poll(void); + virtual void Reset(void); - virtual unsigned int DecodeAudio(const unsigned char *data, int length, - unsigned char *outbuf, int bufsize); +protected: -private: + 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); struct Codec { @@ -69,17 +51,23 @@ private: AVCodecContext *context; }; - Codec m_codecs[eNumCodecs]; - eCodec m_codec; - eCodec m_outputFormat; - ePort m_outputPort; - int m_channels; - int m_samplingRate; - - bool m_passthrough; +private: - AVFrame *m_frame; - cMutex *m_mutex; + 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; }; #endif @@ -0,0 +1,876 @@ +/* + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include "omx.h" + +#include <vdr/tools.h> +#include <vdr/thread.h> + +extern "C" +{ +#include "ilclient.h" +} + +#include "bcm_host.h" + +#define OMX_INIT_STRUCT(a) \ + memset(&(a), 0, sizeof(a)); \ + (a).nSize = sizeof(a); \ + (a).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \ + (a).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \ + (a).nVersion.s.nRevision = OMX_VERSION_REVISION; \ + (a).nVersion.s.nStep = OMX_VERSION_STEP + +#define OMX_AUDIO_CHANNEL_MAPPING(s, c) \ +switch (c) { \ +case 4: \ + (s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \ + (s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \ + (s).eChannelMapping[2] = OMX_AUDIO_ChannelLR; \ + (s).eChannelMapping[3] = OMX_AUDIO_ChannelRR; \ + break; \ +case 1: \ + (s).eChannelMapping[0] = OMX_AUDIO_ChannelCF; \ + break; \ +case 8: \ + (s).eChannelMapping[6] = OMX_AUDIO_ChannelLS; \ + (s).eChannelMapping[7] = OMX_AUDIO_ChannelRS; \ +case 6: \ + (s).eChannelMapping[2] = OMX_AUDIO_ChannelCF; \ + (s).eChannelMapping[3] = OMX_AUDIO_ChannelLFE; \ + (s).eChannelMapping[4] = OMX_AUDIO_ChannelLR; \ + (s).eChannelMapping[5] = OMX_AUDIO_ChannelRR; \ +case 2: \ +default: \ + (s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \ + (s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \ + break; } + + +const char* cOmx::errStr(int err) +{ + return err == OMX_ErrorNone ? "None" : + err == OMX_ErrorInsufficientResources ? "InsufficientResources" : + err == OMX_ErrorUndefined ? "Undefined" : + err == OMX_ErrorInvalidComponentName ? "InvalidComponentName" : + err == OMX_ErrorComponentNotFound ? "ComponentNotFound" : + err == OMX_ErrorInvalidComponent ? "InvalidComponent" : + err == OMX_ErrorBadParameter ? "BadParameter" : + err == OMX_ErrorNotImplemented ? "NotImplemented" : + err == OMX_ErrorUnderflow ? "Underflow" : + err == OMX_ErrorOverflow ? "Overflow" : + err == OMX_ErrorHardware ? "Hardware" : + err == OMX_ErrorInvalidState ? "InvalidState" : + err == OMX_ErrorStreamCorrupt ? "StreamCorrupt" : + err == OMX_ErrorPortsNotCompatible ? "PortsNotCompatible" : + err == OMX_ErrorResourcesLost ? "ResourcesLost" : + err == OMX_ErrorNoMore ? "NoMore" : + err == OMX_ErrorVersionMismatch ? "VersionMismatch" : + err == OMX_ErrorNotReady ? "NotReady" : + err == OMX_ErrorTimeout ? "Timeout" : + err == OMX_ErrorSameState ? "SameState" : + err == OMX_ErrorResourcesPreempted ? "ResourcesPreempted" : + err == OMX_ErrorPortUnresponsiveDuringAllocation ? "PortUnresponsiveDuringAllocation" : + err == OMX_ErrorPortUnresponsiveDuringDeallocation ? "PortUnresponsiveDuringDeallocation" : + err == OMX_ErrorPortUnresponsiveDuringStop ? "PortUnresponsiveDuringStop" : + err == OMX_ErrorIncorrectStateTransition ? "IncorrectStateTransition" : + err == OMX_ErrorIncorrectStateOperation ? "IncorrectStateOperation" : + err == OMX_ErrorUnsupportedSetting ? "UnsupportedSetting" : + err == OMX_ErrorUnsupportedIndex ? "UnsupportedIndex" : + err == OMX_ErrorBadPortIndex ? "BadPortIndex" : + err == OMX_ErrorPortUnpopulated ? "PortUnpopulated" : + err == OMX_ErrorComponentSuspended ? "ComponentSuspended" : + err == OMX_ErrorDynamicResourcesUnavailable ? "DynamicResourcesUnavailable" : + err == OMX_ErrorMbErrorsInFrame ? "MbErrorsInFrame" : + err == OMX_ErrorFormatNotDetected ? "FormatNotDetected" : + err == OMX_ErrorContentPipeOpenFailed ? "ContentPipeOpenFailed" : + err == OMX_ErrorContentPipeCreationFailed ? "ContentPipeCreationFailed" : + err == OMX_ErrorSeperateTablesUsed ? "SeperateTablesUsed" : + err == OMX_ErrorTunnelingUnsupported ? "TunnelingUnsupported" : + err == OMX_ErrorKhronosExtensions ? "KhronosExtensions" : + err == OMX_ErrorVendorStartUnused ? "VendorStartUnused" : + err == OMX_ErrorDiskFull ? "DiskFull" : + err == OMX_ErrorMaxFileSize ? "MaxFileSize" : + err == OMX_ErrorDrmUnauthorised ? "DrmUnauthorised" : + err == OMX_ErrorDrmExpired ? "DrmExpired" : + err == OMX_ErrorDrmGeneral ? "DrmGeneral" : + "unknown"; +} + +void cOmx::HandleEndOfStream(unsigned int portId) + { + dsyslog("rpihddevice: HandleEndOfStream(%d)", portId); + + switch (portId) + { + case 131: + break; + + case 11: + break; + + case 90: + break; + } +} + +void cOmx::HandlePortSettingsChanged(unsigned int portId) +{ + //dsyslog("rpihddevice: HandlePortSettingsChanged(%d)", portId); + + switch (portId) + { + case 131: + + OMX_PARAM_PORTDEFINITIONTYPE portdef; + OMX_INIT_STRUCT(portdef); + portdef.nPortIndex = 131; + if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexParamPortDefinition, + &portdef) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get video decoder port format!"); + + OMX_CONFIG_INTERLACETYPE interlace; + OMX_INIT_STRUCT(interlace); + interlace.nPortIndex = 131; + if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexConfigCommonInterlace, + &interlace) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get video decoder interlace config!"); + + dsyslog("rpihddevice: decoding video %dx%d%s", + portdef.format.video.nFrameWidth, + portdef.format.video.nFrameHeight, + interlace.eMode == OMX_InterlaceProgressive ? "p" : "i"); + + if (ilclient_setup_tunnel(&m_tun[eVideoDecoderToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from video decoder to scheduler!"); + if (ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to enable video scheduler!"); + break; + + case 11: + if (ilclient_setup_tunnel(&m_tun[eVideoSchedulerToVideoRender], 0, 1000) != 0) + esyslog("rpihddevice: failed to setup up tunnel from scheduler to render!"); + if (ilclient_change_component_state(m_comp[eVideoRender], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to enable video render!"); + break; + } +} + +void cOmx::HandleBufferEmpty(COMPONENT_T *comp) +{ + if (comp == m_comp[eVideoDecoder]) + { + m_mutex->Lock(); + m_freeVideoBuffers++; + m_mutex->Unlock(); + } + else if (comp == m_comp[eAudioRender]) + { + m_mutex->Lock(); + m_freeAudioBuffers++; + m_mutex->Unlock(); + } +} + +void cOmx::OnBufferEmpty(void *instance, COMPONENT_T *comp) +{ + cOmx* omx = static_cast <cOmx*> (instance); + omx->HandleBufferEmpty(comp); +} + +void cOmx::OnPortSettingsChanged(void *instance, COMPONENT_T *comp, unsigned int data) +{ + cOmx* omx = static_cast <cOmx*> (instance); + omx->HandlePortSettingsChanged(data); +} + +void cOmx::OnEndOfStream(void *instance, COMPONENT_T *comp, unsigned int data) +{ + cOmx* omx = static_cast <cOmx*> (instance); + omx->HandleEndOfStream(data); +} + +void cOmx::OnError(void *instance, COMPONENT_T *comp, unsigned int data) +{ + if (data != OMX_ErrorSameState) + esyslog("rpihddevice: OmxError(%s)", errStr((int)data)); +} + +cOmx::cOmx() : + m_mutex(new cMutex()), + m_setVideoStartTime(true), + m_setAudioStartTime(true), + m_setVideoDiscontinuity(false), + m_freeAudioBuffers(0), + m_freeVideoBuffers(0), + m_clockReference(eClockRefAudio) +{ +} + +cOmx::~cOmx() +{ + delete m_mutex; +} + +int cOmx::Init(void) +{ + m_client = ilclient_init(); + if (m_client == NULL) + esyslog("rpihddevice: ilclient_init() failed!"); + + if (OMX_Init() != OMX_ErrorNone) + esyslog("rpihddevice: OMX_Init() failed!"); + + ilclient_set_error_callback(m_client, OnError, this); + ilclient_set_empty_buffer_done_callback(m_client, OnBufferEmpty, this); + ilclient_set_port_settings_callback(m_client, OnPortSettingsChanged, this); + ilclient_set_eos_callback(m_client, OnEndOfStream, this); + + // create video_decode + if (ilclient_create_component(m_client, &m_comp[eVideoDecoder], + "video_decode", (ILCLIENT_CREATE_FLAGS_T) + (ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) + esyslog("rpihddevice: failed creating video decoder!"); + + // create video_render + if (ilclient_create_component(m_client, &m_comp[eVideoRender], + "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating video render!"); + + //create clock + if (ilclient_create_component(m_client, &m_comp[eClock], + "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating clock!"); + + // create audio_render + if (ilclient_create_component(m_client, &m_comp[eAudioRender], + "audio_render", (ILCLIENT_CREATE_FLAGS_T) + (ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) + esyslog("rpihddevice: failed creating audio render!"); + + //create video_scheduler + if (ilclient_create_component(m_client, &m_comp[eVideoScheduler], + "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0) + esyslog("rpihddevice: failed creating video scheduler!"); + + // setup tunnels + set_tunnel(&m_tun[eVideoDecoderToVideoScheduler], + m_comp[eVideoDecoder], 131, m_comp[eVideoScheduler], 10); + + set_tunnel(&m_tun[eVideoSchedulerToVideoRender], + m_comp[eVideoScheduler], 11, m_comp[eVideoRender], 90); + + set_tunnel(&m_tun[eClockToVideoScheduler], + m_comp[eClock], 80, m_comp[eVideoScheduler], 12); + + set_tunnel(&m_tun[eClockToAudioRender], + m_comp[eClock], 81, m_comp[eAudioRender], 101); + + // setup clock tunnels first + if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to audio render!"); + + ilclient_change_component_state(m_comp[eClock], OMX_StateExecuting); + ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle); + + // 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; + if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get audio render port parameters!"); + param.nBufferSize = 160 * 1024; + param.nBufferCountActual = m_freeAudioBuffers; + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render port parameters!"); + + OMX_INIT_STRUCT(param); + param.nPortIndex = 130; + 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); + +/* // configure video decoder stall callback + OMX_CONFIG_BUFFERSTALLTYPE stallConf; + OMX_INIT_STRUCT(stallConf); + stallConf.nPortIndex = 131; + stallConf.nDelay = 1500 * 1000; + if (OMX_SetConfig(m_comp[eVideoDecoder], OMX_IndexConfigBufferStall, + &stallConf) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder stall config!"); + + OMX_CONFIG_REQUESTCALLBACKTYPE reqCallback; + OMX_INIT_STRUCT(reqCallback); + reqCallback.nPortIndex = 131; + reqCallback.nIndex = OMX_IndexConfigBufferStall; + reqCallback.bEnable = OMX_TRUE; + if (OMX_SetConfig(m_comp[eVideoDecoder], OMX_IndexConfigRequestCallback, + &reqCallback) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder stall callback!"); +*/ +// 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"); + + return 0; +} + +int cOmx::DeInit(void) +{ + ilclient_teardown_tunnels(m_tun); + ilclient_state_transition(m_comp, OMX_StateIdle); + ilclient_state_transition(m_comp, OMX_StateLoaded); + ilclient_cleanup_components(m_comp); + + OMX_Deinit(); + ilclient_destroy(m_client); + + return 0; +} + +OMX_TICKS cOmx::ToOmxTicks(int64_t val) +{ + OMX_TICKS ticks; + ticks.nLowPart = val; + ticks.nHighPart = val >> 32; + return ticks; +} + +int64_t cOmx::FromOmxTicks(OMX_TICKS &ticks) +{ + int64_t ret = ticks.nLowPart | ((int64_t)(ticks.nHighPart) << 32); + return ret; +} + +void cOmx::PtsToTicks(uint64_t pts, OMX_TICKS &ticks) +{ + // ticks = pts * OMX_TICKS_PER_SECOND / PTSTICKS + pts = pts * 100 / 9; + ticks.nLowPart = pts; + ticks.nHighPart = pts >> 32; +} + +uint64_t cOmx::TicksToPts(OMX_TICKS &ticks) +{ + // pts = ticks * PTSTICKS / OMX_TICKS_PER_SECOND + uint64_t pts = ticks.nHighPart; + pts = (pts << 32) + ticks.nLowPart; + pts = pts * 9 / 100; + return pts; +} + +int64_t cOmx::GetSTC(void) +{ + int64_t stc = -1; +// return stc; + + OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp; + OMX_INIT_STRUCT(timestamp); + timestamp.nPortIndex = OMX_ALL; + + if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), + OMX_IndexConfigTimeCurrentMediaTime, ×tamp) != OMX_ErrorNone) + esyslog("rpihddevice: failed get current clock reference!"); + else + stc = TicksToPts(timestamp.nTimestamp); + +// dsyslog("S %u", timestamp.nTimestamp.nLowPart); + + return stc; +} + +bool cOmx::IsClockRunning(void) +{ + OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; + OMX_INIT_STRUCT(cstate); + + if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), + OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) + esyslog("rpihddevice: failed get clock state!"); + + if (cstate.eState == OMX_TIME_ClockStateRunning) + return true; + else + return false; +} + +void cOmx::SetClockState(eClockState clockState) +{ + m_mutex->Lock(); + + dsyslog("rpihddevice: SetClockState(%s)", + clockState == eClockStateRun ? "eClockStateRun" : + clockState == eClockStateStop ? "eClockStateStop" : + clockState == eClockStateWaitForVideo ? "eClockStateWaitForVideo" : + clockState == eClockStateWaitForAudio ? "eClockStateWaitForAudio" : + clockState == eClockStateWaitForAudioVideo ? "eClockStateWaitForAudioVideo" : + "unknown"); + + OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; + OMX_INIT_STRUCT(cstate); + + if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), + 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 ((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!"); + } + + switch (clockState) + { + case eClockStateRun: + cstate.eState = OMX_TIME_ClockStateRunning; + break; + + case eClockStateStop: + cstate.eState = OMX_TIME_ClockStateStopped; + break; + + case eClockStateWaitForVideo: + cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; + cstate.nWaitMask = OMX_CLOCKPORT0; + m_setVideoStartTime = true; + SetClockReference(eClockRefVideo); + break; + + case eClockStateWaitForAudio: + cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; + cstate.nWaitMask = OMX_CLOCKPORT1; + m_setAudioStartTime = true; + SetClockReference(eClockRefAudio); + break; + + case eClockStateWaitForAudioVideo: + cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; + cstate.nWaitMask = OMX_CLOCKPORT0 | OMX_CLOCKPORT1; + m_setVideoStartTime = true; + m_setAudioStartTime = true; + SetClockReference(eClockRefAudio); + break; + } + + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), + OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set clock state!"); + + m_mutex->Unlock(); +} + +void cOmx::SetClockScale(float scale) +{ + OMX_TIME_CONFIG_SCALETYPE scaleType; + OMX_INIT_STRUCT(scaleType); + scaleType.xScale = floor(scale * pow(2, 16)); + + 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) +{ + OMX_TIME_CONFIG_TIMESTAMPTYPE timeStamp; + OMX_INIT_STRUCT(timeStamp); + timeStamp.nPortIndex = 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!", + m_clockReference == eClockRefAudio ? "audio" : "video"); +} + +unsigned int cOmx::GetMediaTime(void) +{ + unsigned int ret = 0; + + OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp; + OMX_INIT_STRUCT(timestamp); + timestamp.nPortIndex = OMX_ALL; + + if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), + OMX_IndexConfigTimeCurrentMediaTime, ×tamp) != OMX_ErrorNone) + esyslog("rpihddevice: failed get current clock reference!"); + else + ret = timestamp.nTimestamp.nLowPart; + + return ret; +} + +void cOmx::SetClockReference(eClockReference clockReference) +{ + m_clockReference = clockReference; + + OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE refClock; + OMX_INIT_STRUCT(refClock); + refClock.eClock = (m_clockReference == eClockRefAudio) ? + OMX_TIME_RefClockAudio : OMX_TIME_RefClockVideo; + + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), + OMX_IndexConfigTimeActiveRefClock, &refClock) != OMX_ErrorNone) + esyslog("rpihddevice: failed set active clock reference!"); +} + +void cOmx::SetVolume(int vol) +{ + dsyslog("rpihddevice: SetVolumeDevice(%d)", vol); + + OMX_AUDIO_CONFIG_VOLUMETYPE volume; + OMX_INIT_STRUCT(volume); + volume.nPortIndex = 100; + volume.bLinear = OMX_TRUE; + volume.sVolume.nValue = vol * 100 / 255; + + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexConfigAudioVolume, &volume) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set volume!"); +} + +void cOmx::SendEos(void) +{ +#if 0 + OMX_BUFFERHEADERTYPE *buf = ilclient_get_input_buffer(m_comp[eVideoDecoder], 130, 1); + if (buf == NULL) + return; + + buf->nFilledLen = 0; + buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS; + + if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eVideoDecoder]), buf) != OMX_ErrorNone) + esyslog("rpihddevice: failed to send empty packet to video decoder!"); + + if (!m_eosEvent->Wait(10000)) + esyslog("rpihddevice: time out waiting for EOS event!"); +#endif +} + +void cOmx::Stop(void) +{ + // put video decoder into idle + ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle); + + // put video scheduler into idle + ilclient_flush_tunnels(&m_tun[eVideoDecoderToVideoScheduler], 1); + ilclient_disable_tunnel(&m_tun[eVideoDecoderToVideoScheduler]); + ilclient_flush_tunnels(&m_tun[eClockToVideoScheduler], 1); + ilclient_disable_tunnel(&m_tun[eClockToVideoScheduler]); + ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateIdle); + + // put video render into idle + ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1); + ilclient_disable_tunnel(&m_tun[eVideoSchedulerToVideoRender]); + ilclient_change_component_state(m_comp[eVideoRender], OMX_StateIdle); + + // put audio render onto idle + ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); + 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); + + SetClockState(eClockStateStop); +} + +void cOmx::FlushAudio(void) +{ + ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); + + 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) +{ + ilclient_flush_tunnels(&m_tun[eClockToVideoScheduler], 1); + + if (OMX_SendCommand(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_CommandFlush, 130, NULL) != OMX_ErrorNone) + esyslog("rpihddevice: failed to flush video decoder!"); + + ilclient_flush_tunnels(&m_tun[eVideoDecoderToVideoScheduler], 1); + + if (flushRender) + ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1); + + m_setVideoStartTime = true; + m_setVideoDiscontinuity = true; +} + +int cOmx::SetVideoCodec(cVideoCodec::eCodec codec) +{ + // configure video decoder + OMX_VIDEO_PARAM_PORTFORMATTYPE videoFormat; + OMX_INIT_STRUCT(videoFormat); + videoFormat.nPortIndex = 130; + videoFormat.eCompressionFormat = + codec == cVideoCodec::eMPEG2 ? OMX_VIDEO_CodingMPEG2 : + codec == cVideoCodec::eH264 ? OMX_VIDEO_CodingAVC : + OMX_VIDEO_CodingAutoDetect; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), + OMX_IndexParamVideoPortFormat, &videoFormat) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set video decoder parameters!"); + + if (ilclient_enable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL) != 0) + esyslog("rpihddevice: failed to enable port buffer on video decoder!"); + + if (ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateExecuting) != 0) + esyslog("rpihddevice: failed to set video decoder to executing state!"); + + // setup clock tunnels first + if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + return 0; +} + +int cOmx::SetupAudioRender(cAudioCodec::eCodec outputFormat, int channels, int samplingRate, + cAudioPort::ePort audioPort) +{ + // put audio render onto idle + ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); + ilclient_disable_tunnel(&m_tun[eClockToAudioRender]); + ilclient_change_component_state(m_comp[eAudioRender], OMX_StateIdle); + ilclient_disable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL); + + if (OMX_SendCommand(ILC_GET_HANDLE(m_comp[eAudioRender]), OMX_CommandFlush, 100, NULL) != OMX_ErrorNone) + esyslog("rpihddevice: failed to flush audio render!"); + + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + OMX_INIT_STRUCT(format); + format.nPortIndex = 100; + if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone) + esyslog("rpihddevice: failed to get audio port format parameters!"); + + format.eEncoding = + outputFormat == cAudioCodec::ePCM ? OMX_AUDIO_CodingPCM : + outputFormat == cAudioCodec::eMPG ? OMX_AUDIO_CodingMP3 : + outputFormat == cAudioCodec::eAC3 ? OMX_AUDIO_CodingDDP : + outputFormat == cAudioCodec::eEAC3 ? OMX_AUDIO_CodingDDP : + outputFormat == cAudioCodec::eAAC ? OMX_AUDIO_CodingAAC : + outputFormat == cAudioCodec::eDTS ? OMX_AUDIO_CodingDTS : + OMX_AUDIO_CodingAutoDetect; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio port format parameters!"); + + switch (outputFormat) + { + case cAudioCodec::eMPG: + OMX_AUDIO_PARAM_MP3TYPE mp3; + OMX_INIT_STRUCT(mp3); + mp3.nPortIndex = 100; + mp3.nChannels = channels; + mp3.nSampleRate = samplingRate; + mp3.eChannelMode = OMX_AUDIO_ChannelModeStereo; // ? + mp3.eFormat = OMX_AUDIO_MP3StreamFormatMP1Layer3; // should be MPEG-1 layer 2 + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioMp3, &mp3) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render mp3 parameters!"); + break; + + case cAudioCodec::eAC3: + case cAudioCodec::eEAC3: + OMX_AUDIO_PARAM_DDPTYPE ddp; + OMX_INIT_STRUCT(ddp); + ddp.nPortIndex = 100; + ddp.nChannels = channels; + ddp.nSampleRate = samplingRate; + OMX_AUDIO_CHANNEL_MAPPING(ddp, channels); + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioDdp, &ddp) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render ddp parameters!"); + break; + + case cAudioCodec::eAAC: + OMX_AUDIO_PARAM_AACPROFILETYPE aac; + OMX_INIT_STRUCT(aac); + aac.nPortIndex = 100; + aac.nChannels = channels; + aac.nSampleRate = samplingRate; + aac.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4LATM; + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioAac, &aac) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render aac parameters!"); + break; + + case cAudioCodec::eDTS: + OMX_AUDIO_PARAM_DTSTYPE dts; + OMX_INIT_STRUCT(aac); + dts.nPortIndex = 100; + dts.nChannels = channels; + dts.nSampleRate = samplingRate; + dts.nDtsType = 1; // ?? + dts.nFormat = 0; // ?? + dts.nDtsFrameSizeBytes = 0; // ? + OMX_AUDIO_CHANNEL_MAPPING(dts, channels); + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioDts, &dts) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render dts parameters!"); + break; + + case cAudioCodec::ePCM: + OMX_AUDIO_PARAM_PCMMODETYPE pcm; + OMX_INIT_STRUCT(pcm); + pcm.nPortIndex = 100; + pcm.nChannels = channels == 6 ? 8 : channels; + pcm.eNumData = OMX_NumericalDataSigned; + pcm.eEndian = OMX_EndianLittle; + pcm.bInterleaved = OMX_TRUE; + pcm.nBitPerSample = 16; + pcm.nSamplingRate = samplingRate; + pcm.ePCMMode = OMX_AUDIO_PCMModeLinear; + OMX_AUDIO_CHANNEL_MAPPING(pcm, channels); + + if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexParamAudioPcm, &pcm) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio render pcm parameters!"); + break; + + default: + esyslog("rpihddevice: output codec not supported: %s!", + cAudioCodec::Str(outputFormat)); + break; + } + + OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; + OMX_INIT_STRUCT(audioDest); + strcpy((char *)audioDest.sName, + audioPort == cAudioPort::eLocal ? "local" : "hdmi"); + + if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]), + OMX_IndexConfigBrcmAudioDestination, &audioDest) != OMX_ErrorNone) + esyslog("rpihddevice: failed to set audio destination!"); + + if (ilclient_enable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL) != 0) + esyslog("rpihddevice: failed to enable port buffer on audio render!"); + + ilclient_change_component_state(m_comp[eAudioRender], OMX_StateExecuting); + + if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0) + esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); + + return 0; +} + +OMX_BUFFERHEADERTYPE* cOmx::GetAudioBuffer(uint64_t pts) +{ + m_mutex->Lock(); + OMX_BUFFERHEADERTYPE* buf = NULL; + + if (m_freeAudioBuffers > 0) + { + buf = ilclient_get_input_buffer(m_comp[eAudioRender], 100, 0); + if (buf) + { + cOmx::PtsToTicks(pts, buf->nTimeStamp); + buf->nFlags = m_setAudioStartTime ? OMX_BUFFERFLAG_STARTTIME : 0; + buf->nFlags |= pts ? 0 : OMX_BUFFERFLAG_TIME_UNKNOWN; + + m_setAudioStartTime = false; + m_freeAudioBuffers--; + } + } + m_mutex->Unlock(); + return buf; +} + +OMX_BUFFERHEADERTYPE* cOmx::GetVideoBuffer(uint64_t pts) +{ + m_mutex->Lock(); + OMX_BUFFERHEADERTYPE* buf = NULL; + + if (m_freeVideoBuffers > 0) + { + buf = ilclient_get_input_buffer(m_comp[eVideoDecoder], 130, 0); + if (buf) + { + cOmx::PtsToTicks(pts, buf->nTimeStamp); + buf->nFlags = m_setVideoStartTime ? + OMX_BUFFERFLAG_STARTTIME : OMX_BUFFERFLAG_TIME_UNKNOWN; + buf->nFlags |= m_setVideoDiscontinuity ? OMX_BUFFERFLAG_DISCONTINUITY : 0; + + m_setVideoStartTime = false; + m_setVideoDiscontinuity = false; + m_freeVideoBuffers--; + } + } + m_mutex->Unlock(); + return buf; +} + +bool cOmx::PollVideoBuffers(int minBuffers) +{ + return (m_freeVideoBuffers > minBuffers); +} + +bool cOmx::PollAudioBuffers(int minBuffers) +{ + return (m_freeAudioBuffers > minBuffers); +} + +bool cOmx::EmptyAudioBuffer(OMX_BUFFERHEADERTYPE *buf) +{ + if (!buf) + return false; + + return (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eAudioRender]), buf) + == OMX_ErrorNone); +} + +bool cOmx::EmptyVideoBuffer(OMX_BUFFERHEADERTYPE *buf) +{ + if (!buf) + return false; + + return (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eVideoDecoder]), buf) + == OMX_ErrorNone); +} @@ -0,0 +1,122 @@ +/* + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#ifndef OMX_H +#define OMX_H + +#include "types.h" + +extern "C" +{ +#include "ilclient.h" +} + +class cMutex; + +class cOmx +{ + +public: + + cOmx(); + virtual ~cOmx(); + int Init(void); + int DeInit(void); + + static OMX_TICKS ToOmxTicks(int64_t val); + static int64_t FromOmxTicks(OMX_TICKS &ticks); + static void PtsToTicks(uint64_t pts, OMX_TICKS &ticks); + static uint64_t TicksToPts(OMX_TICKS &ticks); + + int64_t GetSTC(void); + bool IsClockRunning(void); + + enum eClockState { + eClockStateRun, + eClockStateStop, + eClockStateWaitForVideo, + eClockStateWaitForAudio, + eClockStateWaitForAudioVideo + }; + + void SetClockState(eClockState clockState); + void SetClockScale(float scale); + void SetMediaTime(uint64_t pts); + unsigned int GetMediaTime(void); + + enum eClockReference { + eClockRefAudio, + eClockRefVideo + }; + + void SetClockReference(eClockReference clockReference); + void SetVolume(int vol); + void SendEos(void); + void Stop(void); + + void FlushAudio(void); + void FlushVideo(bool flushRender = false); + + int SetVideoCodec(cVideoCodec::eCodec codec); + int SetupAudioRender(cAudioCodec::eCodec outputFormat, + int channels, int samplingRate, cAudioPort::ePort audioPort); + + OMX_BUFFERHEADERTYPE* GetAudioBuffer(uint64_t pts = 0); + OMX_BUFFERHEADERTYPE* GetVideoBuffer(uint64_t pts = 0); + bool PollVideoBuffers(int minBuffers = 0); + bool PollAudioBuffers(int minBuffers = 0); + + bool EmptyAudioBuffer(OMX_BUFFERHEADERTYPE *buf); + bool EmptyVideoBuffer(OMX_BUFFERHEADERTYPE *buf); + +private: + + static const char* errStr(int err); + + enum eOmxComponent { + eClock = 0, + eVideoDecoder, + eVideoScheduler, + eVideoRender, + eAudioRender, + eNumComponents + }; + + enum eOmxTunnel { + eVideoDecoderToVideoScheduler = 0, + eVideoSchedulerToVideoRender, + eClockToVideoScheduler, + eClockToAudioRender, + eNumTunnels + }; + + ILCLIENT_T *m_client; + COMPONENT_T *m_comp[cOmx::eNumComponents + 1]; + TUNNEL_T m_tun[cOmx::eNumTunnels + 1]; + + cMutex *m_mutex; + + bool m_setVideoStartTime; + bool m_setAudioStartTime; + bool m_setVideoDiscontinuity; + + int m_freeAudioBuffers; + int m_freeVideoBuffers; + + eClockReference m_clockReference; + + void HandleEndOfStream(unsigned int portId); + void HandlePortSettingsChanged(unsigned int portId); + 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); + +}; + +#endif diff --git a/omxdevice.c b/omxdevice.c index aa5610e..d1893e5 100644 --- a/omxdevice.c +++ b/omxdevice.c @@ -5,6 +5,7 @@ */ #include "omxdevice.h" +#include "omx.h" #include "audio.h" #include "setup.h" @@ -13,860 +14,13 @@ #include <string.h> -extern "C" -{ -#include "ilclient.h" -} - -#include "bcm_host.h" - -#define OMX_INIT_STRUCT(a) \ - memset(&(a), 0, sizeof(a)); \ - (a).nSize = sizeof(a); \ - (a).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \ - (a).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \ - (a).nVersion.s.nRevision = OMX_VERSION_REVISION; \ - (a).nVersion.s.nStep = OMX_VERSION_STEP - -#define OMX_AUDIO_CHANNEL_MAPPING(s, c) \ -switch (c) { \ -case 4: \ - (s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \ - (s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \ - (s).eChannelMapping[2] = OMX_AUDIO_ChannelLR; \ - (s).eChannelMapping[3] = OMX_AUDIO_ChannelRR; \ - break; \ -case 1: \ - (s).eChannelMapping[0] = OMX_AUDIO_ChannelCF; \ - break; \ -case 8: \ - (s).eChannelMapping[6] = OMX_AUDIO_ChannelLS; \ - (s).eChannelMapping[7] = OMX_AUDIO_ChannelRS; \ -case 6: \ - (s).eChannelMapping[2] = OMX_AUDIO_ChannelCF; \ - (s).eChannelMapping[3] = OMX_AUDIO_ChannelLFE; \ - (s).eChannelMapping[4] = OMX_AUDIO_ChannelLR; \ - (s).eChannelMapping[5] = OMX_AUDIO_ChannelRR; \ -case 2: \ -default: \ - (s).eChannelMapping[0] = OMX_AUDIO_ChannelLF; \ - (s).eChannelMapping[1] = OMX_AUDIO_ChannelRF; \ - break; } - -class cOmx -{ - -private: - - static const char* errStr(int err) - { - return err == OMX_ErrorNone ? "None" : - err == OMX_ErrorInsufficientResources ? "InsufficientResources" : - err == OMX_ErrorUndefined ? "Undefined" : - err == OMX_ErrorInvalidComponentName ? "InvalidComponentName" : - err == OMX_ErrorComponentNotFound ? "ComponentNotFound" : - err == OMX_ErrorInvalidComponent ? "InvalidComponent" : - err == OMX_ErrorBadParameter ? "BadParameter" : - err == OMX_ErrorNotImplemented ? "NotImplemented" : - err == OMX_ErrorUnderflow ? "Underflow" : - err == OMX_ErrorOverflow ? "Overflow" : - err == OMX_ErrorHardware ? "Hardware" : - err == OMX_ErrorInvalidState ? "InvalidState" : - err == OMX_ErrorStreamCorrupt ? "StreamCorrupt" : - err == OMX_ErrorPortsNotCompatible ? "PortsNotCompatible" : - err == OMX_ErrorResourcesLost ? "ResourcesLost" : - err == OMX_ErrorNoMore ? "NoMore" : - err == OMX_ErrorVersionMismatch ? "VersionMismatch" : - err == OMX_ErrorNotReady ? "NotReady" : - err == OMX_ErrorTimeout ? "Timeout" : - err == OMX_ErrorSameState ? "SameState" : - err == OMX_ErrorResourcesPreempted ? "ResourcesPreempted" : - err == OMX_ErrorPortUnresponsiveDuringAllocation ? "PortUnresponsiveDuringAllocation" : - err == OMX_ErrorPortUnresponsiveDuringDeallocation ? "PortUnresponsiveDuringDeallocation" : - err == OMX_ErrorPortUnresponsiveDuringStop ? "PortUnresponsiveDuringStop" : - err == OMX_ErrorIncorrectStateTransition ? "IncorrectStateTransition" : - err == OMX_ErrorIncorrectStateOperation ? "IncorrectStateOperation" : - err == OMX_ErrorUnsupportedSetting ? "UnsupportedSetting" : - err == OMX_ErrorUnsupportedIndex ? "UnsupportedIndex" : - err == OMX_ErrorBadPortIndex ? "BadPortIndex" : - err == OMX_ErrorPortUnpopulated ? "PortUnpopulated" : - err == OMX_ErrorComponentSuspended ? "ComponentSuspended" : - err == OMX_ErrorDynamicResourcesUnavailable ? "DynamicResourcesUnavailable" : - err == OMX_ErrorMbErrorsInFrame ? "MbErrorsInFrame" : - err == OMX_ErrorFormatNotDetected ? "FormatNotDetected" : - err == OMX_ErrorContentPipeOpenFailed ? "ContentPipeOpenFailed" : - err == OMX_ErrorContentPipeCreationFailed ? "ContentPipeCreationFailed" : - err == OMX_ErrorSeperateTablesUsed ? "SeperateTablesUsed" : - err == OMX_ErrorTunnelingUnsupported ? "TunnelingUnsupported" : - err == OMX_ErrorKhronosExtensions ? "KhronosExtensions" : - err == OMX_ErrorVendorStartUnused ? "VendorStartUnused" : - err == OMX_ErrorDiskFull ? "DiskFull" : - err == OMX_ErrorMaxFileSize ? "MaxFileSize" : - err == OMX_ErrorDrmUnauthorised ? "DrmUnauthorised" : - err == OMX_ErrorDrmExpired ? "DrmExpired" : - err == OMX_ErrorDrmGeneral ? "DrmGeneral" : - "unknown"; - }; - - enum eOmxComponent { - eClock = 0, - eVideoDecoder, - eVideoScheduler, - eVideoRender, - eAudioRender, - eNumComponents - }; - - enum eOmxTunnel { - eVideoDecoderToVideoScheduler = 0, - eVideoSchedulerToVideoRender, - eClockToVideoScheduler, - eClockToAudioRender, - eNumTunnels - }; - - // to do: make this private! - - ILCLIENT_T *m_client; - COMPONENT_T *m_comp[cOmx::eNumComponents + 1]; - TUNNEL_T m_tun[cOmx::eNumTunnels + 1]; - - cMutex *m_mutex; - - bool m_firstVideoBuffer; - bool m_firstAudioBuffer; - - int m_freeAudioBuffers; - int m_freeVideoBuffers; - - void HandleEndOfStream(unsigned int portId) - { - dsyslog("rpihddevice: HandleEndOfStream(%d)", portId); - - switch (portId) - { - case 131: - break; - - case 11: - break; - - case 90: - break; - } - } - - void HandlePortSettingsChanged(unsigned int portId) - { - //dsyslog("rpihddevice: HandlePortSettingsChanged(%d)", portId); - - switch (portId) - { - case 131: - - OMX_PARAM_PORTDEFINITIONTYPE portdef; - OMX_INIT_STRUCT(portdef); - portdef.nPortIndex = 131; - if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexParamPortDefinition, - &portdef) != OMX_ErrorNone) - esyslog("rpihddevice: failed to get video decoder port format!"); - - OMX_CONFIG_INTERLACETYPE interlace; - OMX_INIT_STRUCT(interlace); - interlace.nPortIndex = 131; - if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eVideoDecoder]), OMX_IndexConfigCommonInterlace, - &interlace) != OMX_ErrorNone) - esyslog("rpihddevice: failed to get video decoder interlace config!"); - - dsyslog("rpihddevice: decoding video %dx%d%s", - portdef.format.video.nFrameWidth, - portdef.format.video.nFrameHeight, - interlace.eMode == OMX_InterlaceProgressive ? "p" : "i"); - - if (ilclient_setup_tunnel(&m_tun[eVideoDecoderToVideoScheduler], 0, 0) != 0) - esyslog("rpihddevice: failed to setup up tunnel from video decoder to scheduler!"); - if (ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateExecuting) != 0) - esyslog("rpihddevice: failed to enable video scheduler!"); - break; - - case 11: - if (ilclient_setup_tunnel(&m_tun[eVideoSchedulerToVideoRender], 0, 1000) != 0) - esyslog("rpihddevice: failed to setup up tunnel from scheduler to render!"); - if (ilclient_change_component_state(m_comp[eVideoRender], OMX_StateExecuting) != 0) - esyslog("rpihddevice: failed to enable video render!"); - break; - } - } - - void HandleBufferEmpty(COMPONENT_T *comp) - { - if (comp == m_comp[eVideoDecoder]) - { - m_mutex->Lock(); - m_freeVideoBuffers++; - m_mutex->Unlock(); - } - else if (comp == m_comp[eAudioRender]) - { - m_mutex->Lock(); - m_freeAudioBuffers++; - m_mutex->Unlock(); - } - } - - static void OnBufferEmpty(void *instance, COMPONENT_T *comp) - { - cOmx* omx = static_cast <cOmx*> (instance); - omx->HandleBufferEmpty(comp); - } - - static void OnPortSettingsChanged(void *instance, COMPONENT_T *comp, unsigned int data) - { - cOmx* omx = static_cast <cOmx*> (instance); - omx->HandlePortSettingsChanged(data); - } - - static void OnEndOfStream(void *instance, COMPONENT_T *comp, unsigned int data) - { - cOmx* omx = static_cast <cOmx*> (instance); - omx->HandleEndOfStream(data); - } - - static void OnError(void *instance, COMPONENT_T *comp, unsigned int data) - { - if (data != OMX_ErrorSameState) - esyslog("rpihddevice: OmxError(%s)", errStr((int)data)); - } - -public: - - cOmx() : - m_mutex(new cMutex()), - m_firstVideoBuffer(true), - m_firstAudioBuffer(true), - m_freeAudioBuffers(0), - m_freeVideoBuffers(0) - { } - - virtual ~cOmx() - { - delete m_mutex; - } - - int Init(void) - { - m_client = ilclient_init(); - if (m_client == NULL) - esyslog("rpihddevice: ilclient_init() failed!"); - - if (OMX_Init() != OMX_ErrorNone) - esyslog("rpihddevice: OMX_Init() failed!"); - - ilclient_set_error_callback(m_client, OnError, this); - ilclient_set_empty_buffer_done_callback(m_client, OnBufferEmpty, this); - ilclient_set_port_settings_callback(m_client, OnPortSettingsChanged, this); - ilclient_set_eos_callback(m_client, OnEndOfStream, this); - - // create video_decode - if (ilclient_create_component(m_client, &m_comp[eVideoDecoder], - "video_decode", (ILCLIENT_CREATE_FLAGS_T) - (ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) - esyslog("rpihddevice: failed creating video decoder!"); - - // create video_render - if (ilclient_create_component(m_client, &m_comp[eVideoRender], - "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0) - esyslog("rpihddevice: failed creating video render!"); - - //create clock - if (ilclient_create_component(m_client, &m_comp[eClock], - "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0) - esyslog("rpihddevice: failed creating clock!"); - - // create audio_render - if (ilclient_create_component(m_client, &m_comp[eAudioRender], - "audio_render", (ILCLIENT_CREATE_FLAGS_T) - (ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS)) != 0) - esyslog("rpihddevice: failed creating audio render!"); - - //create video_scheduler - if (ilclient_create_component(m_client, &m_comp[eVideoScheduler], - "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0) - esyslog("rpihddevice: failed creating video scheduler!"); - - // setup tunnels - set_tunnel(&m_tun[eVideoDecoderToVideoScheduler], m_comp[eVideoDecoder], 131, m_comp[eVideoScheduler], 10); - set_tunnel(&m_tun[eVideoSchedulerToVideoRender], m_comp[eVideoScheduler], 11, m_comp[eVideoRender], 90); - set_tunnel(&m_tun[eClockToVideoScheduler], m_comp[eClock], 80, m_comp[eVideoScheduler], 12); - set_tunnel(&m_tun[eClockToAudioRender], m_comp[eClock], 81, m_comp[eAudioRender], 101); - - // setup clock tunnels first - if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0) - esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); - - if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0) - esyslog("rpihddevice: failed to setup up tunnel from clock to audio render!"); - - OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE refclock; - OMX_INIT_STRUCT(refclock); - refclock.eClock = OMX_TIME_RefClockAudio; - // refclock.eClock = OMX_TIME_RefClockVideo; - - if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), - OMX_IndexConfigTimeActiveRefClock, &refclock) != OMX_ErrorNone) - esyslog("rpihddevice: failed set active clock reference!"); - - ilclient_change_component_state(m_comp[eClock], OMX_StateExecuting); - ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle); - - // set up the number and size of buffers for audio render - m_freeAudioBuffers = 16; - OMX_PARAM_PORTDEFINITIONTYPE param; - OMX_INIT_STRUCT(param); - param.nPortIndex = 100; - if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) - esyslog("rpihddevice: failed to get audio render port parameters!"); - param.nBufferSize = 128 * 1024; - param.nBufferCountActual = m_freeAudioBuffers; - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamPortDefinition, ¶m) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render port parameters!"); - - OMX_INIT_STRUCT(param); - param.nPortIndex = 130; - 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); - -/* // configure video decoder stall callback - OMX_CONFIG_BUFFERSTALLTYPE stallConf; - OMX_INIT_STRUCT(stallConf); - stallConf.nPortIndex = 131; - stallConf.nDelay = 1500 * 1000; - if (OMX_SetConfig(m_comp[eVideoDecoder], OMX_IndexConfigBufferStall, - &stallConf) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set video decoder stall config!"); - - OMX_CONFIG_REQUESTCALLBACKTYPE reqCallback; - OMX_INIT_STRUCT(reqCallback); - reqCallback.nPortIndex = 131; - reqCallback.nIndex = OMX_IndexConfigBufferStall; - reqCallback.bEnable = OMX_TRUE; - if (OMX_SetConfig(m_comp[eVideoDecoder], OMX_IndexConfigRequestCallback, - &reqCallback) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set video decoder stall callback!"); -*/ - // if (ilclient_enable_port_buffers(comp[eAudioRender], 100, NULL, NULL, NULL) != 0) - // esyslog("rpihddevice: failed to enable port buffer on audio render!"); - - return 0; - } - - int DeInit(void) - { - ilclient_teardown_tunnels(m_tun); - ilclient_state_transition(m_comp, OMX_StateIdle); - ilclient_state_transition(m_comp, OMX_StateLoaded); - ilclient_cleanup_components(m_comp); - - OMX_Deinit(); - ilclient_destroy(m_client); - - return 0; - } - - static void PtsToTicks(uint64_t pts, OMX_TICKS &ticks) - { - // ticks = pts * OMX_TICKS_PER_SECOND / PTSTICKS - pts = pts * 100 / 9; - ticks.nLowPart = (OMX_U32)pts; - ticks.nHighPart = (OMX_U32)(pts >> 32); - } - - static uint64_t TicksToPts(OMX_TICKS &ticks) - { - // pts = ticks * PTSTICKS / OMX_TICKS_PER_SECOND - uint64_t pts = ticks.nHighPart; - pts = (pts << 32) + ticks.nLowPart; - pts = pts * 9 / 100; - return pts; - } - - int64_t GetSTC(void) - { - int64_t stc = -1; - - OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp; - OMX_INIT_STRUCT(timestamp); - timestamp.nPortIndex = OMX_ALL; - - if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), - OMX_IndexConfigTimeCurrentMediaTime, ×tamp) != OMX_ErrorNone) - esyslog("rpihddevice: failed get current clock reference!"); - else - stc = TicksToPts(timestamp.nTimestamp); - - return stc; - } - - bool IsClockRunning(void) - { - OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; - OMX_INIT_STRUCT(cstate); - - if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), - OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) - esyslog("rpihddevice: failed get clock state!"); - - if (cstate.eState == OMX_TIME_ClockStateRunning) - return true; - else - return false; - } - - enum eClockState { - eClockStateRun, - eClockStateStop, - eClockStateWaitForVideo, - eClockStateWaitForAudio, - eClockStateWaitForAudioVideo - }; - - void SetClockState(eClockState clockState) - { - dsyslog("rpihddevice: SetClockState(%s)", - clockState == eClockStateRun ? "eClockStateRun" : - clockState == eClockStateStop ? "eClockStateStop" : - clockState == eClockStateWaitForVideo ? "eClockStateWaitForVideo" : - clockState == eClockStateWaitForAudio ? "eClockStateWaitForAudio" : - clockState == eClockStateWaitForAudioVideo ? "eClockStateWaitForAudioVideo" : "unknown"); - - OMX_TIME_CONFIG_CLOCKSTATETYPE cstate; - OMX_INIT_STRUCT(cstate); - - if (OMX_GetConfig(ILC_GET_HANDLE(m_comp[eClock]), - OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) - esyslog("rpihddevice: failed get clock state!"); - - 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 set clock state!"); - } - - switch (clockState) - { - case eClockStateRun: - cstate.eState = OMX_TIME_ClockStateRunning; - break; - - case eClockStateStop: - cstate.eState = OMX_TIME_ClockStateStopped; - break; - - case eClockStateWaitForVideo: - cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT0; - m_firstVideoBuffer = true; - break; - - case eClockStateWaitForAudio: - cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT1; - m_firstAudioBuffer = true; - break; - - case eClockStateWaitForAudioVideo: - cstate.eState = OMX_TIME_ClockStateWaitingForStartTime; - cstate.nWaitMask = OMX_CLOCKPORT0 | OMX_CLOCKPORT1; - m_firstVideoBuffer = true; - m_firstAudioBuffer = true; - break; - } - - if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eClock]), - OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone) - esyslog("rpihddevice: failed set clock state!"); - } - - void SetClockScale(float scale) - { - OMX_TIME_CONFIG_SCALETYPE scaleType; - OMX_INIT_STRUCT(scaleType); - scaleType.xScale = floor(scale * pow(2, 16)); - 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 SetVolume(int vol) - { - dsyslog("rpihddevice: SetVolumeDevice(%d)", vol); - - OMX_AUDIO_CONFIG_VOLUMETYPE volume; - OMX_INIT_STRUCT(volume); - volume.nPortIndex = 100; - volume.bLinear = OMX_TRUE; - volume.sVolume.nValue = vol * 100 / 255; - - if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexConfigAudioVolume, &volume) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set volume!"); - } - - void SendEos(void) - { -#if 0 - OMX_BUFFERHEADERTYPE *buf = ilclient_get_input_buffer(m_comp[eVideoDecoder], 130, 1); - if (buf == NULL) - return; - - buf->nFilledLen = 0; - buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS; - - if (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eVideoDecoder]), buf) != OMX_ErrorNone) - esyslog("rpihddevice: failed to send empty packet to video decoder!"); - - if (!m_eosEvent->Wait(10000)) - esyslog("rpihddevice: time out waiting for EOS event!"); -#endif - } - - void Stop(void) - { - // put video decoder into idle - ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateIdle); - - // put video scheduler into idle - ilclient_flush_tunnels(&m_tun[eVideoDecoderToVideoScheduler], 1); - ilclient_disable_tunnel(&m_tun[eVideoDecoderToVideoScheduler]); - ilclient_flush_tunnels(&m_tun[eClockToVideoScheduler], 1); - ilclient_disable_tunnel(&m_tun[eClockToVideoScheduler]); - ilclient_change_component_state(m_comp[eVideoScheduler], OMX_StateIdle); - - // put video render into idle - ilclient_flush_tunnels(&m_tun[eVideoSchedulerToVideoRender], 1); - ilclient_disable_tunnel(&m_tun[eVideoSchedulerToVideoRender]); - ilclient_change_component_state(m_comp[eVideoRender], OMX_StateIdle); - - // put audio render onto idle - ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); - 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); - - SetClockState(eClockStateStop); - } - - int SetVideoCodec(cOmxDevice::eVideoCodec codec) - { - // configure video decoder - OMX_VIDEO_PARAM_PORTFORMATTYPE videoFormat; - OMX_INIT_STRUCT(videoFormat); - videoFormat.nPortIndex = 130; - videoFormat.eCompressionFormat = - codec == cOmxDevice::eMPEG2 ? OMX_VIDEO_CodingMPEG2 : - codec == cOmxDevice::eH264 ? OMX_VIDEO_CodingAVC : - OMX_VIDEO_CodingAutoDetect; - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eVideoDecoder]), - OMX_IndexParamVideoPortFormat, &videoFormat) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set video decoder parameters!"); - - 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"); - - if (ilclient_enable_port_buffers(m_comp[eVideoDecoder], 130, NULL, NULL, NULL) != 0) - esyslog("rpihddevice: failed to enable port buffer on video decoder!"); - - if (ilclient_change_component_state(m_comp[eVideoDecoder], OMX_StateExecuting) != 0) - esyslog("rpihddevice: failed to set video decoder to executing state!"); - - // setup clock tunnels first - if (ilclient_setup_tunnel(&m_tun[eClockToVideoScheduler], 0, 0) != 0) - esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); - - return 0; - } - - int SetupAudioRender(cAudioDecoder::eCodec outputFormat, int channels, int samplingRate, - cAudioDecoder::ePort audioPort) - { - // put audio render onto idle - ilclient_flush_tunnels(&m_tun[eClockToAudioRender], 1); - ilclient_disable_tunnel(&m_tun[eClockToAudioRender]); - ilclient_change_component_state(m_comp[eAudioRender], OMX_StateIdle); - ilclient_disable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL); - - OMX_AUDIO_PARAM_PORTFORMATTYPE format; - OMX_INIT_STRUCT(format); - format.nPortIndex = 100; - if (OMX_GetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone) - esyslog("rpihddevice: failed to get audio port format parameters!"); - - format.eEncoding = - outputFormat == cAudioDecoder::ePCM ? OMX_AUDIO_CodingPCM : - outputFormat == cAudioDecoder::eMPG ? OMX_AUDIO_CodingMP3 : - outputFormat == cAudioDecoder::eAC3 ? OMX_AUDIO_CodingDDP : - outputFormat == cAudioDecoder::eEAC3 ? OMX_AUDIO_CodingDDP : - outputFormat == cAudioDecoder::eAAC ? OMX_AUDIO_CodingAAC : - outputFormat == cAudioDecoder::eDTS ? OMX_AUDIO_CodingDTS : - OMX_AUDIO_CodingAutoDetect; - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioPortFormat, &format) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio port format parameters!"); - - switch (outputFormat) - { - case cAudioDecoder::eMPG: - OMX_AUDIO_PARAM_MP3TYPE mp3; - OMX_INIT_STRUCT(mp3); - mp3.nPortIndex = 100; - mp3.nChannels = channels; - mp3.nSampleRate = samplingRate; - mp3.eChannelMode = OMX_AUDIO_ChannelModeStereo; // ? - mp3.eFormat = OMX_AUDIO_MP3StreamFormatMP1Layer3; // should be MPEG-1 layer 2 - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioMp3, &mp3) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render mp3 parameters!"); - break; - - case cAudioDecoder::eAC3: - case cAudioDecoder::eEAC3: - OMX_AUDIO_PARAM_DDPTYPE ddp; - OMX_INIT_STRUCT(ddp); - ddp.nPortIndex = 100; - ddp.nChannels = channels; - ddp.nSampleRate = samplingRate; - OMX_AUDIO_CHANNEL_MAPPING(ddp, channels); - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioDdp, &ddp) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render ddp parameters!"); - break; - - case cAudioDecoder::eAAC: - OMX_AUDIO_PARAM_AACPROFILETYPE aac; - OMX_INIT_STRUCT(aac); - aac.nPortIndex = 100; - aac.nChannels = channels; - aac.nSampleRate = samplingRate; - aac.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4LATM; - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioAac, &aac) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render aac parameters!"); - break; - - case cAudioDecoder::eDTS: - OMX_AUDIO_PARAM_DTSTYPE dts; - OMX_INIT_STRUCT(aac); - dts.nPortIndex = 100; - dts.nChannels = channels; - dts.nSampleRate = samplingRate; - dts.nDtsType = 1; // ?? - dts.nFormat = 0; // ?? - dts.nDtsFrameSizeBytes = 0; // ? - OMX_AUDIO_CHANNEL_MAPPING(dts, channels); - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioDts, &dts) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render dts parameters!"); - break; - - case cAudioDecoder::ePCM: - OMX_AUDIO_PARAM_PCMMODETYPE pcm; - OMX_INIT_STRUCT(pcm); - pcm.nPortIndex = 100; - pcm.nChannels = channels == 6 ? 8 : channels; - pcm.eNumData = OMX_NumericalDataSigned; - pcm.eEndian = OMX_EndianLittle; - pcm.bInterleaved = OMX_TRUE; - pcm.nBitPerSample = 16; - pcm.nSamplingRate = samplingRate; - pcm.ePCMMode = OMX_AUDIO_PCMModeLinear; - OMX_AUDIO_CHANNEL_MAPPING(pcm, channels); - - if (OMX_SetParameter(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexParamAudioPcm, &pcm) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio render pcm parameters!"); - break; - - default: - break; - } - - OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; - OMX_INIT_STRUCT(audioDest); - strcpy((char *)audioDest.sName, - audioPort == cAudioDecoder::eLocal ? "local" : "hdmi"); - - if (OMX_SetConfig(ILC_GET_HANDLE(m_comp[eAudioRender]), - OMX_IndexConfigBrcmAudioDestination, &audioDest) != OMX_ErrorNone) - esyslog("rpihddevice: failed to set audio destination!"); - - if (ilclient_enable_port_buffers(m_comp[eAudioRender], 100, NULL, NULL, NULL) != 0) - esyslog("rpihddevice: failed to enable port buffer on audio render!"); - - ilclient_change_component_state(m_comp[eAudioRender], OMX_StateExecuting); - - if (ilclient_setup_tunnel(&m_tun[eClockToAudioRender], 0, 0) != 0) - esyslog("rpihddevice: failed to setup up tunnel from clock to video scheduler!"); - - return 0; - } - - OMX_BUFFERHEADERTYPE* GetAudioBuffer(uint64_t pts = 0) - { - m_mutex->Lock(); - OMX_BUFFERHEADERTYPE* buf = NULL; - - if (m_freeAudioBuffers > 0) - { - buf = ilclient_get_input_buffer(m_comp[eAudioRender], 100, 0); - - if (buf != NULL) - { - cOmx::PtsToTicks(pts, buf->nTimeStamp); - buf->nFlags = m_firstAudioBuffer ? OMX_BUFFERFLAG_STARTTIME : 0; //OMX_BUFFERFLAG_TIME_UNKNOWN; - m_firstAudioBuffer = false; - m_freeAudioBuffers--; - } - } - m_mutex->Unlock(); - return buf; - } - - OMX_BUFFERHEADERTYPE* GetVideoBuffer(uint64_t pts = 0) - { - m_mutex->Lock(); - OMX_BUFFERHEADERTYPE* buf = NULL; - - if (m_freeVideoBuffers > 0) - { - buf = ilclient_get_input_buffer(m_comp[eVideoDecoder], 130, 0); - - if (buf != NULL) - { - cOmx::PtsToTicks(pts, buf->nTimeStamp); - buf->nFlags = m_firstVideoBuffer ? OMX_BUFFERFLAG_STARTTIME : 0; //OMX_BUFFERFLAG_TIME_UNKNOWN; - m_firstVideoBuffer = false; - m_freeVideoBuffers--; - } - } - m_mutex->Unlock(); - return buf; - } - - bool VideoBuffersAvailable(void) - { - return (m_freeVideoBuffers > 0); - } - - bool EmptyAudioBuffer(OMX_BUFFERHEADERTYPE *buf) - { - if (!buf) - return false; - - return (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eAudioRender]), buf) == OMX_ErrorNone); - } - - bool EmptyVideoBuffer(OMX_BUFFERHEADERTYPE *buf) - { - if (!buf) - return false; - - return (OMX_EmptyThisBuffer(ILC_GET_HANDLE(m_comp[eVideoDecoder]), buf) == OMX_ErrorNone); - } -}; - -/* ------------------------------------------------------------------------- */ -#if 0 -class cAudio -{ - -public: - - cAudio() : - sampleRate(0), - bitDepth(0), - nChannels(0), - encoding(0), - m_handle(0) - { - int ret; - mpg123_init(); - m_handle = mpg123_new(NULL, &ret); - if (m_handle == NULL) - esyslog("rpihddevice: failed to create mpg123 handle!"); - - if (mpg123_open_feed(m_handle) == MPG123_ERR) - esyslog("rpihddevice: failed to open mpg123 feed!"); - - dsyslog("rpihddevice: new cAudio()"); - } - - ~cAudio() - { - mpg123_delete(m_handle); - dsyslog("rpihddevice: delete cAudio()"); - } - - bool writeData(const unsigned char *buf, unsigned int length) - { - return (mpg123_feed(m_handle, buf, length) != MPG123_ERR); - } - - unsigned int readSamples(unsigned char *buf, unsigned length, bool &done) - { - unsigned int read = 0; - done = (mpg123_read(m_handle, buf, length, &read) == MPG123_NEED_MORE); - mpg123_getformat(m_handle, &sampleRate, &nChannels, &encoding); - return read; - } - - long sampleRate; - int bitDepth; - int nChannels; - int encoding; - - mpg123_handle *m_handle; -}; -#endif -/* ------------------------------------------------------------------------- */ - cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) : cDevice(), m_onPrimaryDevice(onPrimaryDevice), m_omx(new cOmx()), - m_audio(new cAudioDecoder()), + m_audio(new cAudioDecoder(m_omx)), m_mutex(new cMutex()), - m_state(eStop), - m_audioCodecReady(false), - m_videoCodecReady(false), + m_state(eNone), m_audioId(0) { } @@ -880,14 +34,14 @@ cOmxDevice::~cOmxDevice() int cOmxDevice::Init(void) { - if (m_audio->Init() < 0) + if (m_omx->Init() < 0) { - esyslog("rpihddevice: failed to initialize audio!"); + esyslog("rpihddevice: failed to initialize OMX!"); return -1; } - if (m_omx->Init() < 0) + if (m_audio->Init() < 0) { - esyslog("rpihddevice: failed to initialize OMX!"); + esyslog("rpihddevice: failed to initialize audio!"); return -1; } return 0; @@ -895,14 +49,14 @@ int cOmxDevice::Init(void) int cOmxDevice::DeInit(void) { - if (m_omx->DeInit() < 0) + if (m_audio->DeInit() < 0) { - esyslog("rpihddevice: failed to deinitialize OMX!"); + esyslog("rpihddevice: failed to deinitialize audio!"); return -1; } - if (m_audio->DeInit() < 0) + if (m_omx->DeInit() < 0) { - esyslog("rpihddevice: failed to deinitialize audio!"); + esyslog("rpihddevice: failed to deinitialize OMX!"); return -1; } return 0; @@ -917,7 +71,7 @@ bool cOmxDevice::CanReplay(void) const { dsyslog("rpihddevice: CanReplay"); // video codec de-initialization done - return (m_state == eStop); + return (m_state == eNone); } bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) @@ -936,19 +90,19 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) { case pmNone: m_mutex->Lock(); - m_state = eStop; + if (HasVideo()) + m_omx->FlushVideo(true); + if (HasAudio()) + { + m_audio->Reset(); + m_omx->FlushAudio(); + } m_omx->Stop(); - m_audioCodecReady = false; - m_videoCodecReady = false; + SetState(eNone); m_mutex->Unlock(); break; case pmAudioVideo: - m_mutex->Lock(); - m_state = eStarting; - m_mutex->Unlock(); - break; - case pmAudioOnly: case pmAudioOnlyBlack: case pmVideoOnly: @@ -961,153 +115,123 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id) { m_mutex->Lock(); + int ret = Length; - if (m_state == eStarting) - { - m_omx->SetClockState(cOmx::eClockStateWaitForAudio); - m_state = ePlay; - } - else if (m_state != ePlay) - { - m_mutex->Unlock(); - dsyslog("rpihddevice: PlayAudio() not replaying!"); - return 0; - } + // if first packet is audio, we assume audio only + if (State() == eNone) + SetState(eAudioOnly); - if (!PesHasLength(Data)) + if (State() == eAudioOnly || State() == eAudioVideo) { - esyslog("rpihddevice: empty audio packet dropped!"); - m_mutex->Unlock(); - return Length; - } - - int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - const uchar *payload = Data + PesPayloadOffset(Data); - int length = PesLength(Data) - PesPayloadOffset(Data); - - if (m_audioId != Id) - { - m_audioId = Id; - m_audioCodecReady = false; - } - - // try to init codec - if (!m_audioCodecReady || cRpiSetup::HasAudioSetupChanged()) - { - if (m_audio->SetupAudioCodec(Data, Length)) + if (m_audio->Poll()) { - m_audioCodecReady = true; - m_omx->SetupAudioRender( - m_audio->GetOutputFormat(), - m_audio->GetChannels(), - m_audio->GetSamplingrate(), - m_audio->GetOutputPort()); + if (m_audioId != Id) + { + m_audioId = Id; + m_audio->Reset(); + } + + //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 - { - m_mutex->Unlock(); - return Length; - } - } - - OMX_BUFFERHEADERTYPE *buf = m_omx->GetAudioBuffer(pts); - if (buf == NULL) - { - m_mutex->Unlock(); - return 0; - } - - // decode and write audio packet - buf->nFilledLen = m_audio->DecodeAudio(payload, length, buf->pBuffer, buf->nAllocLen); - - // if decoding failed, reset audio codec - if (!buf->nFilledLen) - m_audioCodecReady = false; - - if (!m_omx->EmptyAudioBuffer(buf)) - { - m_mutex->Unlock(); - return 0; + ret = 0; } m_mutex->Unlock(); - return Length; + return ret; } int cOmxDevice::PlayVideo(const uchar *Data, int Length) { m_mutex->Lock(); + int ret = Length; - if (m_state == eStarting) - { - m_omx->SetClockState(cOmx::eClockStateWaitForAudioVideo); - m_state = ePlay; - } - else if (m_state != ePlay) - { - m_mutex->Unlock(); - dsyslog("rpihddevice: PlayVideo() not replaying!"); - return 0; - } + if (State() == eNone) + SetState(eStartingVideo); - if (!PesHasLength(Data)) + if (State() == eStartingVideo) { - esyslog("rpihddevice: empty video packet dropped!"); - m_mutex->Unlock(); - return Length; + cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length); + if (codec != cVideoCodec::eInvalid) + { + 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)); + } + } } - int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - - const uchar *payload = Data + PesPayloadOffset(Data); - int length = PesLength(Data) - PesPayloadOffset(Data); - - // try to init codec if PTS is valid - if (!m_videoCodecReady && pts != 0) + if (State() == eVideoOnly || State() == eAudioVideo) { - eVideoCodec codec = GetVideoCodec(Data, Length); - if (cRpiSetup::IsVideoCodecSupported(codec)) + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; + OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); + if (buf) { - m_videoCodecReady = (m_omx->SetVideoCodec(codec) == 0); - dsyslog("rpihddevice: set video codec to %s!", - VideoCodecStr(codec)); + //dsyslog("V %llu", PesGetPts(Data)); + const uchar *payload = Data + PesPayloadOffset(Data); + int length = PesLength(Data) - PesPayloadOffset(Data); + if (length <= buf->nAllocLen) + { + memcpy(buf->pBuffer, payload, length); + buf->nFilledLen = length; + } + 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!"); + } } } - if (!m_videoCodecReady) - { - m_mutex->Unlock(); - return Length; - } - OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); - if (buf == NULL) - { - //esyslog("rpihddevice: failed to get video buffer!"); - m_mutex->Unlock(); - return 0; - } + m_mutex->Unlock(); + return ret; +} - if (length <= buf->nAllocLen) +void cOmxDevice::SetState(eState state) +{ + switch (state) { - memcpy(buf->pBuffer, payload, length); - buf->nFilledLen = length; - } - else - esyslog("rpihddevice: video packet too long for video buffer!"); + case eNone: + m_omx->SetClockState(cOmx::eClockStateStop); + break; -// dsyslog("V: %u.%u - f:%d %lld", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, buf->nFlags, pts); -// dsyslog("rpihddevice: PlayVideo(%u.%u, %02x %02x %02x %02x %02x %02x %02x %02x, %d)", buf->nTimeStamp.nHighPart, buf->nTimeStamp.nLowPart, -// buf->pBuffer[0], buf->pBuffer[1], buf->pBuffer[2], buf->pBuffer[3], -// buf->pBuffer[4], buf->pBuffer[5], buf->pBuffer[6], buf->pBuffer[7], buf->nFilledLen); - if (!m_omx->EmptyVideoBuffer(buf)) - esyslog("rpihddevice: failed to pass buffer to video decoder!"); + case eStartingVideo: + break; - m_mutex->Unlock(); - return Length; + 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(); } @@ -1139,7 +263,22 @@ bool cOmxDevice::Flush(int TimeoutMs) void cOmxDevice::Clear(void) { + m_mutex->Lock(); dsyslog("rpihddevice: Clear()"); + + m_omx->SetClockScale(1.0f); + m_omx->SetMediaTime(0); + + if (HasAudio()) + { + m_audio->Reset(); + m_omx->FlushAudio(); + } + + if (HasVideo()) + m_omx->FlushVideo(false); + + m_mutex->Unlock(); cDevice::Clear(); } @@ -1152,7 +291,7 @@ bool cOmxDevice::Poll(cPoller &Poller, int TimeoutMs) { cTimeMs time; time.Set(); - while (!m_omx->VideoBuffersAvailable()) + while (!m_omx->PollVideoBuffers() || !m_audio->Poll()) { if (time.Elapsed() >= TimeoutMs) return false; @@ -1168,26 +307,38 @@ void cOmxDevice::MakePrimaryDevice(bool On) cDevice::MakePrimaryDevice(On); } -cOmxDevice::eVideoCodec cOmxDevice::GetVideoCodec(const uchar *data, int length) +cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length) { + if (!PesHasPts(data)) + return cVideoCodec::eInvalid; + if (PesLength(data) - PesPayloadOffset(data) < 6) - return eUnknown; + 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 + if ((!p[i] && !p[i + 1] && p[i + 2] == 0x01)) + { + if (p[i + 3] == 0xb3) // sequence header + return cVideoCodec::eMPEG2; - if (p[0] != 0x00 || p[1] != 0x00) - return eUnknown; - - if (p[2] == 0x01 && p[3] == 0xb3) - return eMPEG2; - - else if ((p[2] == 0x01 && p[3] == 0x09 && p[4] == 0x10) || - (p[2] == 0x00 && p[3] == 0x01 && p[4] == 0x09 && p[5] == 0x10)) - return eH264; - - //esyslog("rpihddevice: invalid start sequence: " - // "%02x %02x %02x %02x %02x %02x %02x %02x", - // p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); - - return eUnknown; + else if (p[i + 3] == 0x09) // slice + { + 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 + default: + return cVideoCodec::eInvalid; + } + } + return cVideoCodec::eInvalid; + } + } + return cVideoCodec::eInvalid; } diff --git a/omxdevice.h b/omxdevice.h index affe2a9..52a94a3 100644 --- a/omxdevice.h +++ b/omxdevice.h @@ -10,6 +10,8 @@ #include <vdr/device.h> #include <vdr/thread.h> +#include "types.h" + class cOmx; class cAudioDecoder; @@ -19,23 +21,13 @@ class cOmxDevice : cDevice public: enum eState { - eStop, - eStarting, - ePlay - }; - - enum eVideoCodec { - eMPEG2, - eH264, - eUnknown + eNone, + eStartingVideo, + eAudioOnly, + eVideoOnly, + eAudioVideo }; - static const char* VideoCodecStr(eVideoCodec codec) - { - return (codec == eMPEG2) ? "MPEG2" : - (codec == eH264) ? "H264" : "unknown"; - } - cOmxDevice(void (*onPrimaryDevice)(void)); virtual ~cOmxDevice(); @@ -74,17 +66,26 @@ private: void (*m_onPrimaryDevice)(void); - virtual eVideoCodec GetVideoCodec(const uchar *data, int length); + 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; + }; + + inline bool HasAudio() { + return m_state == eAudioOnly || + m_state == eAudioVideo; + }; cOmx *m_omx; cAudioDecoder *m_audio; cMutex *m_mutex; eState m_state; - - bool m_audioCodecReady; - bool m_videoCodecReady; - uchar m_audioId; }; @@ -279,7 +279,6 @@ void cOvgOsd::Flush(void) pm->ViewPort().Width() * sizeof(tColor), pm->Data()); delete pm; } - return; } diff --git a/rpihddevice.c b/rpihddevice.c index fb90c11..fa21315 100644 --- a/rpihddevice.c +++ b/rpihddevice.c @@ -10,8 +10,9 @@ #include "ovgosd.h" #include "omxdevice.h" #include "setup.h" +#include "types.h" -static const char *VERSION = "0.0.4"; +static const char *VERSION = "0.0.5"; static const char *DESCRIPTION = "HD output device for Raspberry Pi"; class cDummyDevice : cDevice @@ -79,7 +80,8 @@ bool cPluginRpiHdDevice::Initialize(void) if (!cRpiSetup::HwInit()) return false; - if (!cRpiSetup::IsVideoCodecSupported(cOmxDevice::eMPEG2)) + // test whether MPEG2 license is available + if (!cRpiSetup::IsVideoCodecSupported(cVideoCodec::eMPEG2)) esyslog("rpihddevice: WARNING: MPEG2 video decoder not enabled!"); m_device = new cOmxDevice(&OnPrimaryDevice); @@ -111,4 +113,4 @@ bool cPluginRpiHdDevice::SetupParse(const char *Name, const char *Value) return cRpiSetup::GetInstance()->Parse(Name, Value); } -VDRPLUGINCREATOR(cPluginRpiHdDevice); // Don't touch this! +VDRPLUGINCREATOR(cPluginRpiHdDevice); // Don't touch this! okay. @@ -44,8 +44,10 @@ public: m_newAudioPort = *m_audioPort; m_newPassthrough = *m_passthrough; - Add(new cMenuEditStraItem(tr("Audio Port"), &m_newAudioPort, 2, audioport)); - Add(new cMenuEditBoolItem(tr("Digital Audio Pass-Through"), &m_newPassthrough)); + Add(new cMenuEditStraItem( + tr("Audio Port"), &m_newAudioPort, 2, audioport)); + Add(new cMenuEditBoolItem( + tr("Digital Audio Pass-Through"), &m_newPassthrough)); } protected: @@ -108,14 +110,20 @@ bool cRpiSetup::HwInit(void) return true; } -bool cRpiSetup::IsAudioFormatSupported(cAudioDecoder::eCodec codec, int channels, int samplingRate) +bool cRpiSetup::IsAudioFormatSupported(cAudioCodec::eCodec codec, + int channels, int samplingRate) { + // AAC and DTS pass-through currently not supported + if (codec == cAudioCodec::eAAC || + codec == cAudioCodec::eDTS) + return false; + if (vc_tv_hdmi_audio_supported( - codec == cAudioDecoder::eMPG ? EDID_AudioFormat_eMPEG1 : - codec == cAudioDecoder::eAC3 ? EDID_AudioFormat_eAC3 : - codec == cAudioDecoder::eEAC3 ? EDID_AudioFormat_eEAC3 : - codec == cAudioDecoder::eAAC ? EDID_AudioFormat_eAAC : - codec == cAudioDecoder::eDTS ? EDID_AudioFormat_eDTS : + codec == cAudioCodec::eMPG ? EDID_AudioFormat_eMPEG1 : + 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 : @@ -123,7 +131,8 @@ bool cRpiSetup::IsAudioFormatSupported(cAudioDecoder::eCodec codec, int channels samplingRate == 96000 ? EDID_AudioSampleRate_e96KHz : samplingRate == 176000 ? EDID_AudioSampleRate_e176KHz : samplingRate == 192000 ? EDID_AudioSampleRate_e192KHz : - EDID_AudioSampleRate_e48KHz, EDID_AudioSampleSize_16bit) == 0) + EDID_AudioSampleRate_e48KHz, + EDID_AudioSampleSize_16bit) == 0) return true; return false; @@ -7,10 +7,8 @@ #ifndef SETUP_H #define SETUP_H -#include "audio.h" -#include "omxdevice.h" - -class cMenuSetupPage; +#include "omx.h" +#include "types.h" class cRpiSetup { @@ -19,16 +17,20 @@ public: static bool HwInit(void); - static cAudioDecoder::ePort GetAudioPort(void) { - return (GetInstance()->m_audioPort) ? cAudioDecoder::eHDMI : cAudioDecoder::eLocal; } - static bool IsAudioPassthrough(void) { return GetInstance()->m_passthrough; } + static cAudioPort::ePort GetAudioPort(void) { + return (GetInstance()->m_audioPort) ? cAudioPort::eHDMI : cAudioPort::eLocal; } + + static bool IsAudioPassthrough(void) { + return GetInstance()->m_passthrough; } + static bool HasAudioSetupChanged(void); - static bool IsAudioFormatSupported(cAudioDecoder::eCodec codec, int channels, int samplingRate); + static bool IsAudioFormatSupported(cAudioCodec::eCodec codec, + int channels, int samplingRate); - static bool IsVideoCodecSupported(cOmxDevice::eVideoCodec codec) { - return codec == cOmxDevice::eMPEG2 ? GetInstance()->m_mpeg2Enabled : - codec == cOmxDevice::eH264 ? true : false; + static bool IsVideoCodecSupported(cVideoCodec::eCodec codec) { + return codec == cVideoCodec::eMPEG2 ? GetInstance()->m_mpeg2Enabled : + codec == cVideoCodec::eH264 ? true : false; } static int GetDisplaySize(int &width, int &height, double &aspect); @@ -36,7 +38,7 @@ public: static cRpiSetup* GetInstance(void); static void DropInstance(void); - cMenuSetupPage* GetSetupPage(void); + class cMenuSetupPage* GetSetupPage(void); bool Parse(const char *name, const char *value); private: @@ -0,0 +1,67 @@ +/* + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#ifndef TYPES_H +#define TYPES_H + +class cAudioCodec +{ +public: + + enum eCodec { + ePCM, + eMPG, + eAC3, + eEAC3, + eAAC, + eDTS, + eNumCodecs, + eInvalid + }; + + static const char* Str(eCodec codec) { + return (codec == ePCM) ? "PCM" : + (codec == eMPG) ? "MPEG" : + (codec == eAC3) ? "AC3" : + (codec == eEAC3) ? "E-AC3" : + (codec == eAAC) ? "AAC" : + (codec == eDTS) ? "DTS" : "unknown"; + } +}; + +class cVideoCodec +{ +public: + + enum eCodec { + eMPEG2, + eH264, + eNumCodecs, + eInvalid + }; + + static const char* Str(eCodec codec) { + return (codec == eMPEG2) ? "MPEG2" : + (codec == eH264) ? "H264" : "unknown"; + } +}; + +class cAudioPort +{ +public: + + enum ePort { + eLocal, + eHDMI + }; + + static const char* Str(ePort port) { + return (port == eLocal) ? "local" : + (port == eHDMI) ? "HDMI" : "unknown"; + } +}; + +#endif |