diff --git a/HISTORY b/HISTORY index 189cc36..8c36726 100644 --- a/HISTORY +++ b/HISTORY @@ -365,7 +365,8 @@ VDR Plugin 'femon' Revision History - Cleaned up compilation warnings. - Fixed font handling to be thread-safe. -2009-07-xx: Version 1.7.3 +2009-08-28: Version 1.7.3 - Removed OSD offset and height options. - Added PES assembler. +- Added bitstream parsers for all codecs. diff --git a/femonh264.c b/femonh264.c index b3197b9..670dabf 100644 --- a/femonh264.c +++ b/femonh264.c @@ -42,9 +42,23 @@ const eVideoFormat cFemonH264::s_VideoFormats[] = VIDEO_FORMAT_RESERVED }; +const uint8_t cFemonH264::s_SeiNumClockTsTable[9] = +{ + 1, 1, 1, 2, 2, 3, 3, 2, 3 +}; cFemonH264::cFemonH264(cFemonVideoIf *videohandler) -: m_VideoHandler(videohandler) +: m_VideoHandler(videohandler), + m_Width(0), + m_Height(0), + m_AspectRatio(VIDEO_ASPECT_RATIO_INVALID), + m_Format(VIDEO_FORMAT_INVALID), + m_FrameRate(0), + m_BitRate(0), + m_Scan(VIDEO_SCAN_INVALID), + m_CpbDpbDelaysPresentFlag(false), + m_PicStructPresentFlag(false), + m_TimeOffsetLength(0) { } @@ -54,7 +68,10 @@ cFemonH264::~cFemonH264() bool cFemonH264::processVideo(const uint8_t *buf, int len) { - bool aud_found = false, sps_found = false, sei_found = true; // sei currently disabled + uint8_t nal_data[len]; + bool aud_found = false, sps_found = false, sei_found = false; + const uint8_t *start = buf; + const uint8_t *end = start + len; if (!m_VideoHandler) return false; @@ -63,10 +80,7 @@ bool cFemonH264::processVideo(const uint8_t *buf, int len) if (!PesLongEnough(len)) return false; buf += PesPayloadOffset(buf); - - const uint8_t *start = buf; - const uint8_t *end = start + len; - uint8_t nal_data[len]; + start = buf; for (;;) { int consumed = 0; @@ -81,7 +95,6 @@ bool cFemonH264::processVideo(const uint8_t *buf, int len) switch (buf[4] >> 5) { case 0: case 3: case 5: // I_FRAME //Dprintf("H.264: Found NAL AUD at offset %d/%d", buf - start, len); - m_VideoHandler->SetVideoCodec(VIDEO_CODEC_H264); aud_found = true; break; case 1: case 4: case 6: // P_FRAME; @@ -101,7 +114,7 @@ bool cFemonH264::processVideo(const uint8_t *buf, int len) sps_found = true; } break; - +#if 0 case NAL_SEI: if (!sei_found) { //Dprintf("H.264: Found NAL SEI at offset %d/%d", buf - start, len); @@ -111,7 +124,7 @@ bool cFemonH264::processVideo(const uint8_t *buf, int len) sei_found = true; } break; - +#endif default: break; } @@ -122,6 +135,22 @@ bool cFemonH264::processVideo(const uint8_t *buf, int len) buf += consumed + 4; } + if (aud_found) { + m_VideoHandler->SetVideoCodec(VIDEO_CODEC_H264); + if (sps_found) { + //Dprintf("H.264 SPS: -> video size %dx%d, aspect %d format %d", m_Width, m_Height, m_AspectRatio, m_Format); + m_VideoHandler->SetVideoFormat(m_Format); + m_VideoHandler->SetVideoSize(m_Width, m_Height); + m_VideoHandler->SetVideoAspectRatio(m_AspectRatio); + } + if (sei_found) { + //Dprintf("H.264 SEI: -> stream bitrate %.1f, frame rate %.1f scan %d", m_BitRate, m_FrameRate, m_Scan); + m_VideoHandler->SetVideoFramerate(m_FrameRate); + m_VideoHandler->SetVideoBitrate(m_BitRate); + m_VideoHandler->SetVideoScan(m_Scan); + } + } + return aud_found; } @@ -159,11 +188,16 @@ int cFemonH264::nalUnescape(uint8_t *dst, const uint8_t *src, int len) int cFemonH264::parseSPS(const uint8_t *buf, int len) { int profile_idc, pic_order_cnt_type, frame_mbs_only, i, j; - unsigned int width = 0, height = 0; - eVideoAspectRatio aspect_ratio = VIDEO_ASPECT_RATIO_INVALID; - eVideoFormat format = VIDEO_FORMAT_INVALID; cBitStream bs(buf, len); + unsigned int width = m_Width; + unsigned int height = m_Height; + eVideoAspectRatio aspect_ratio = m_AspectRatio; + eVideoFormat format = m_Format; + bool cpb_dpb_delays_present_flag = m_CpbDpbDelaysPresentFlag; + bool pic_struct_present_flag = m_PicStructPresentFlag; + unsigned int time_offset_length = m_TimeOffsetLength; + profile_idc = bs.getU8(); //Dprintf("H.264 SPS: profile_idc %d", profile_idc); @@ -255,21 +289,83 @@ int cFemonH264::parseSPS(const uint8_t *buf, int len) if (bs.getBit()) // overscan_info_present_flag bs.skipBit(); // overscan_approriate_flag if (bs.getBit()) { // video_signal_type_present_flag - uint32_t video_format = bs.getBits(3); + uint32_t video_format; + video_format = bs.getBits(3); // video_format if (video_format < sizeof(s_VideoFormats) / sizeof(s_VideoFormats[0])) { format = s_VideoFormats[video_format]; //Dprintf("H.264 SPS: -> video format %d", format); } + bs.skipBit(); // video_full_range_flag + bs.skipBit(); // video_full_range_flag + if (bs.getBit()) { // colour_description_present_flag + bs.skipBits(8); // colour_primaries + bs.skipBits(8); // transfer_characteristics + bs.skipBits(8); // matrix_coefficients + } + } + if (bs.getBit()) { // chroma_loc_info_present_flag + bs.skipUeGolomb(); // chroma_sample_loc_type_top_field + bs.skipUeGolomb(); // chroma_sample_loc_type_bottom_field + } + if (bs.getBit()) { // timing_info_present_flag + bs.skipBits(32); // num_units_in_tick + bs.skipBits(32); // time_scale + bs.skipBit(); // fixed_frame_rate_flag + } + int nal_hrd_parameters_present_flag = bs.getBit(); // nal_hrd_parameters_present_flag + if (nal_hrd_parameters_present_flag) { + int cpb_cnt_minus1; + cpb_cnt_minus1 = bs.getUeGolomb(); // cpb_cnt_minus1 + bs.skipBits(4); // bit_rate_scale + bs.skipBits(4); // cpb_size_scale + for (int i = 0; i < cpb_cnt_minus1; ++i) { + bs.skipUeGolomb(); // bit_rate_value_minus1[i] + bs.skipUeGolomb(); // cpb_size_value_minus1[i] + bs.skipBit(); // cbr_flag[i] + } + bs.skipBits(5); // initial_cpb_removal_delay_length_minus1 + bs.skipBits(5); // cpb_removal_delay_length_minus1 + bs.skipBits(5); // dpb_output_delay_length_minus1 + time_offset_length = bs.getBits(5); // time_offset_length + } + int vlc_hrd_parameters_present_flag = bs.getBit(); // vlc_hrd_parameters_present_flag + if (vlc_hrd_parameters_present_flag) { + int cpb_cnt_minus1; + cpb_cnt_minus1 = bs.getUeGolomb(); // cpb_cnt_minus1 + bs.skipBits(4); // bit_rate_scale + bs.skipBits(4); // cpb_size_scale + for (int i = 0; i < cpb_cnt_minus1; ++i) { + bs.skipUeGolomb(); // bit_rate_value_minus1[i] + bs.skipUeGolomb(); // cpb_size_value_minus1[i] + bs.skipBit(); // cbr_flag[i] + } + bs.skipBits(5); // initial_cpb_removal_delay_length_minus1 + bs.skipBits(5); // cpb_removal_delay_length_minus1 + bs.skipBits(5); // dpb_output_delay_length_minus1 + time_offset_length = bs.getBits(5); // time_offset_length + } + cpb_dpb_delays_present_flag = (nal_hrd_parameters_present_flag | vlc_hrd_parameters_present_flag); + if (cpb_dpb_delays_present_flag) + bs.skipBit(); // low_delay_hrd_flag + pic_struct_present_flag = bs.getBit(); // pic_struct_present_flag + if (bs.getBit()) { // bitstream_restriction_flag + bs.skipBit(); // motion_vectors_over_pic_boundaries_flag + bs.skipUeGolomb(); // max_bytes_per_pic_denom + bs.skipUeGolomb(); // max_bits_per_mb_denom + bs.skipUeGolomb(); // log2_max_mv_length_horizontal + bs.skipUeGolomb(); // log2_max_mv_length_vertical + bs.skipUeGolomb(); // num_reorder_frames + bs.skipUeGolomb(); // max_dec_frame_buffering } } - //Dprintf("H.264 SPS: -> video size %dx%d, aspect %d", width, height, aspect_ratio); - - if (m_VideoHandler) { - m_VideoHandler->SetVideoFormat(format); - m_VideoHandler->SetVideoSize(width, height); - m_VideoHandler->SetVideoAspectRatio(aspect_ratio); - } + m_Width = width; + m_Height = height; + m_AspectRatio = aspect_ratio; + m_Format = format; + m_CpbDpbDelaysPresentFlag = cpb_dpb_delays_present_flag; + m_PicStructPresentFlag = pic_struct_present_flag; + m_TimeOffsetLength = time_offset_length; return (bs.getIndex() / 8); } @@ -277,10 +373,12 @@ int cFemonH264::parseSPS(const uint8_t *buf, int len) int cFemonH264::parseSEI(const uint8_t *buf, int len) { int num_referenced_subseqs, i; - double frame_rate = 0, bit_rate = 0; - eVideoScan scan = VIDEO_SCAN_INVALID; cBitStream bs(buf, len); + double frame_rate = m_FrameRate; + double bit_rate = m_BitRate; + eVideoScan scan = m_Scan; + while ((bs.getIndex() * 8 + 16) < len) { // sei_message int lastByte, payloadSize = 0, payloadType = 0; @@ -297,23 +395,60 @@ int cFemonH264::parseSEI(const uint8_t *buf, int len) } while (lastByte == 0xFF); switch (payloadType) { // sei_payload - //case 1: // pic_timing - // ... - // switch (bs.getBits(2)) { // ct_type - // case 0: - // scan = VIDEO_SCAN_PROGRESSIVE; - // break; - // case 1: - // scan = VIDEO_SCAN_INTERLACED; - // break; - // case 2: - // scan = VIDEO_SCAN_UNKNOWN; - // break; - // default: - // scan = VIDEO_SCAN_RESERVED; - // break; - // } - // break; + case 1: // pic_timing + if (m_CpbDpbDelaysPresentFlag) { // cpb_dpb_delays_present_flag + bs.skipUeGolomb(); // cpb_removal_delay + bs.skipUeGolomb(); // dpb_output_delay + } + if (m_PicStructPresentFlag) { // pic_struct_present_flag + unsigned int pic_struct = bs.getBits(4); // pic_struct + if (pic_struct >= (sizeof(s_SeiNumClockTsTable) ) / sizeof(s_SeiNumClockTsTable[0])) + return 0; + for (int i = 0; i < s_SeiNumClockTsTable[pic_struct]; ++i) { + if (bs.getBit()) { // clock_timestamp_flag[i] + int full_timestamp_flag; + switch (bs.getBits(2)) { // ct_type + case 0: + scan = VIDEO_SCAN_PROGRESSIVE; + break; + case 1: + scan = VIDEO_SCAN_INTERLACED; + break; + case 2: + scan = VIDEO_SCAN_UNKNOWN; + break; + default: + scan = VIDEO_SCAN_RESERVED; + break; + } + //Dprintf("\nH.264 SEI: -> scan type %d", bit_rate, scan); + bs.skipBit(); // nuit_field_based_flag + bs.skipBits(5); // counting_type + full_timestamp_flag = bs.getBit(); // full_timestamp_flag + bs.skipBit(); // discontinuity_flag + bs.skipBit(); // cnt_dropped_flag + bs.skipBits(8); // n_frames + if (full_timestamp_flag) { + bs.skipBits(6); // seconds_value + bs.skipBits(6); // minutes_value + bs.skipBits(5); // hours_value + } + else { + if (bs.getBit()) { // seconds_flag + bs.skipBits(6); // seconds_value + if (bs.getBit()) { // minutes_flag + bs.skipBits(6); // minutes_value + if (bs.getBit()) // hours_flag + bs.skipBits(5); // hours_value + } + } + } + if (m_TimeOffsetLength > 0) + bs.skipBits(m_TimeOffsetLength); // time_offset + } + } + } + break; case 12: // sub_seq_characteristics bs.skipUeGolomb(); // sub_seq_layer_num @@ -322,9 +457,9 @@ int cFemonH264::parseSEI(const uint8_t *buf, int len) bs.skipBits(32); // sub_seq_duration if (bs.getBit()) { // average_rate_flag bs.skipBit(); // accurate_statistics_flag - bit_rate = bs.getU16(); // average_bit_rate - frame_rate = bs.getU16(); // average_frame_rate - //Dprintf("H.264 SEI: -> stream bitrate %.1f, frame rate %.1f", sei->bitrate, sei->frame_rate); + bit_rate = bs.getU16() / 1048.51; // average_bit_rate (1000 bit/s -> Mbit/s) + frame_rate = bs.getU16() / 256.0; // average_frame_rate (frames/256s) + //Dprintf("\nH.264 SEI: -> stream bitrate %.1f, frame rate %.1f", bit_rate, frame_rate); } num_referenced_subseqs = bs.getUeGolomb(); // num_referenced_subseqs for (i = 0; i < num_referenced_subseqs; ++i) { @@ -343,11 +478,9 @@ int cFemonH264::parseSEI(const uint8_t *buf, int len) bs.byteAlign(); } - if (m_VideoHandler) { - m_VideoHandler->SetVideoFramerate(frame_rate); - m_VideoHandler->SetVideoBitrate(bit_rate); - m_VideoHandler->SetVideoScan(scan); - } + m_FrameRate = frame_rate; + m_BitRate = bit_rate; + m_Scan = scan; return (bs.getIndex() / 8); } diff --git a/femonh264.h b/femonh264.h index 41c15e5..7fa597e 100644 --- a/femonh264.h +++ b/femonh264.h @@ -20,6 +20,16 @@ private: }; cFemonVideoIf *m_VideoHandler; + unsigned int m_Width; + unsigned int m_Height; + eVideoAspectRatio m_AspectRatio; + eVideoFormat m_Format; + double m_FrameRate; + double m_BitRate; + eVideoScan m_Scan; + bool m_CpbDpbDelaysPresentFlag; + bool m_PicStructPresentFlag; + unsigned int m_TimeOffsetLength; const uint8_t *nextStartCode(const uint8_t *start, const uint8_t *end); int nalUnescape(uint8_t *dst, const uint8_t *src, int len); @@ -28,6 +38,7 @@ private: static const eVideoAspectRatio s_AspectRatios[]; static const eVideoFormat s_VideoFormats[]; + static const uint8_t s_SeiNumClockTsTable[9]; public: cFemonH264(cFemonVideoIf *videohandler); diff --git a/femonosd.c b/femonosd.c index aa23456..13df81f 100644 --- a/femonosd.c +++ b/femonosd.c @@ -493,7 +493,7 @@ void cFemonOsd::Action(void) DrawStatusWindow(); } else if (m_SvdrpConnection.handle >= 0) { - cmd.handle = m_SvdrpConnection.handle; + cmd.handle = m_SvdrpConnection.handle; m_SvdrpPlugin->Service("SvdrpCommand-v1.0", &cmd); if (cmd.responseCode == 900) { for (cLine *line = cmd.reply.First(); line; line = cmd.reply.Next(line)) { @@ -576,7 +576,7 @@ void cFemonOsd::Show(void) if (channel) { IS_AUDIO_TRACK(track) ? apid[0] = channel->Apid(int(track - ttAudioFirst)) : apid[0] = channel->Apid(0); IS_DOLBY_TRACK(track) ? dpid[0] = channel->Dpid(int(track - ttDolbyFirst)) : dpid[0] = channel->Dpid(0); - m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vpid(), apid, dpid); + m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vtype(), channel->Vpid(), apid, dpid); cDevice::ActualDevice()->AttachReceiver(m_Receiver); } } @@ -621,7 +621,7 @@ void cFemonOsd::ChannelSwitch(const cDevice * device, int channelNumber) if (channel) { IS_AUDIO_TRACK(track) ? apid[0] = channel->Apid(int(track - ttAudioFirst)) : apid[0] = channel->Apid(0); IS_DOLBY_TRACK(track) ? dpid[0] = channel->Dpid(int(track - ttDolbyFirst)) : dpid[0] = channel->Dpid(0); - m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vpid(), apid, dpid); + m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vtype(), channel->Vpid(), apid, dpid); cDevice::ActualDevice()->AttachReceiver(m_Receiver); } } @@ -642,7 +642,7 @@ void cFemonOsd::SetAudioTrack(int Index, const char * const *Tracks) if (channel) { IS_AUDIO_TRACK(track) ? apid[0] = channel->Apid(int(track - ttAudioFirst)) : apid[0] = channel->Apid(0); IS_DOLBY_TRACK(track) ? dpid[0] = channel->Dpid(int(track - ttDolbyFirst)) : dpid[0] = channel->Dpid(0); - m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vpid(), apid, dpid); + m_Receiver = new cFemonReceiver(channel->GetChannelID(), channel->Ca(), channel->Vtype(), channel->Vpid(), apid, dpid); cDevice::ActualDevice()->AttachReceiver(m_Receiver); } } @@ -693,7 +693,7 @@ bool cFemonOsd::SvdrpConnect(void) m_SvdrpPlugin->Service("SvdrpConnection-v1.0", &m_SvdrpConnection); if (m_SvdrpConnection.handle >= 0) { SvdrpCommand_v1_0 cmd; - cmd.handle = m_SvdrpConnection.handle; + cmd.handle = m_SvdrpConnection.handle; cmd.command = cString::sprintf("PLUG %s\r\n", PLUGIN_NAME_I18N); m_SvdrpPlugin->Service("SvdrpCommand-v1.0", &cmd); if (cmd.responseCode != 214) { @@ -716,7 +716,7 @@ bool cFemonOsd::SvdrpTune(void) cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); if (channel) { SvdrpCommand_v1_0 cmd; - cmd.handle = m_SvdrpConnection.handle; + cmd.handle = m_SvdrpConnection.handle; cmd.command = cString::sprintf("CHAN %s\r\n", *channel->GetChannelID().ToString()); m_SvdrpPlugin->Service("SvdrpCommand-v1.0", &cmd); if (cmd.responseCode == 250) @@ -765,7 +765,7 @@ double cFemonOsd::GetDolbyBitrate(void) } eOSState cFemonOsd::ProcessKey(eKeys Key) -{ +{ eOSState state = cOsdObject::ProcessKey(Key); if (state == osUnknown) { switch (Key) { diff --git a/femonreceiver.c b/femonreceiver.c index 57b327d..4d0003a 100644 --- a/femonreceiver.c +++ b/femonreceiver.c @@ -10,7 +10,7 @@ #include "femoncfg.h" #include "femonreceiver.h" -cFemonReceiver::cFemonReceiver(tChannelID ChannelID, int Ca, int Vpid, int Apid[], int Dpid[]) +cFemonReceiver::cFemonReceiver(tChannelID ChannelID, int Ca, int Vtype, int Vpid, int Apid[], int Dpid[]) : cReceiver(ChannelID, -1, Vpid, Apid, Dpid, NULL), cThread("femon receiver"), m_Sleep(), @@ -19,6 +19,7 @@ cFemonReceiver::cFemonReceiver(tChannelID ChannelID, int Ca, int Vpid, int Apid[ m_DetectMPEG(this, this), m_DetectAAC(this), m_DetectAC3(this), + m_VideoType(Vtype), m_VideoPid(Vpid), m_VideoPacketCount(0), m_VideoBitrate(0.0), @@ -28,7 +29,7 @@ cFemonReceiver::cFemonReceiver(tChannelID ChannelID, int Ca, int Vpid, int Apid[ m_AudioBitrate(0.0), m_AudioValid(false), m_AC3Pid(Dpid[0]), - m_AC3PacketCount(0), + m_AC3PacketCount(0), m_AC3Bitrate(0), m_AC3Valid(false) { @@ -93,10 +94,18 @@ void cFemonReceiver::Receive(uchar *Data, int Length) m_VideoPacketCount++; if (TsPayloadStart(Data)) { while (const uint8_t *p = m_VideoAssembler.GetPes(len)) { - if (m_DetectMPEG.processVideo(p, len) || m_DetectH264.processVideo(p, len)) { - m_VideoValid = true; - break; - } + if (m_VideoType == 0x1B) { // MPEG4 + if (m_DetectH264.processVideo(p, len)) { + m_VideoValid = true; + break; + } + } + else { + if (m_DetectMPEG.processVideo(p, len)) { + m_VideoValid = true; + break; + } + } } m_VideoAssembler.Reset(); } diff --git a/femonreceiver.h b/femonreceiver.h index e65d759..fea121d 100644 --- a/femonreceiver.h +++ b/femonreceiver.h @@ -30,6 +30,7 @@ private: cFemonAC3 m_DetectAC3; cTsToPes m_VideoAssembler; + int m_VideoType; int m_VideoPid; int m_VideoPacketCount; double m_VideoBitrate; @@ -81,7 +82,7 @@ public: virtual void SetAC3LFE(bool onoff) { m_AC3Info.lfe = onoff; } public: - cFemonReceiver(tChannelID ChannelID, int Ca, int Vpid, int Apid[], int Dpid[]); + cFemonReceiver(tChannelID ChannelID, int Ca, int Vtype, int Vpid, int Apid[], int Dpid[]); virtual ~cFemonReceiver(); void Deactivate(void); diff --git a/femontools.h b/femontools.h index 2324fea..eed6ca0 100644 --- a/femontools.h +++ b/femontools.h @@ -79,23 +79,25 @@ public: cBitStream(const uint8_t *buf, const int len); ~cBitStream(); - int getBit(); - uint32_t getBits(uint32_t n); - void skipBits(uint32_t n); - uint32_t getUeGolomb(); - int32_t getSeGolomb(); - void skipGolomb(); - void skipUeGolomb(); - void skipSeGolomb(); - void byteAlign(); - void skipBit() { skipBits(1); } - uint32_t getU8() { return getBits(8); } - uint32_t getU16() { return ((getBits(8) << 8) | getBits(8)); } - uint32_t getU24() { return ((getBits(8) << 16) | (getBits(8) << 8) | getBits(8)); } - uint32_t getU32() { return ((getBits(8) << 24) | (getBits(8) << 16) | (getBits(8) << 8) | getBits(8)); } - bool isEOF() { return (index >= count); } - void reset() { index = 0; } - int getIndex() { return (isEOF() ? count : index);} + int getBit(); + uint32_t getBits(uint32_t n); + void skipBits(uint32_t n); + uint32_t getUeGolomb(); + int32_t getSeGolomb(); + void skipGolomb(); + void skipUeGolomb(); + void skipSeGolomb(); + void byteAlign(); + + void skipBit() { skipBits(1); } + uint32_t getU8() { return getBits(8); } + uint32_t getU16() { return ((getBits(8) << 8) | getBits(8)); } + uint32_t getU24() { return ((getBits(8) << 16) | (getBits(8) << 8) | getBits(8)); } + uint32_t getU32() { return ((getBits(8) << 24) | (getBits(8) << 16) | (getBits(8) << 8) | getBits(8)); } + bool isEOF() { return (index >= count); } + void reset() { index = 0; } + int getIndex() { return (isEOF() ? count : index); } + const uint8_t *getData() { return (isEOF() ? NULL : data + (index / 8)); } }; #endif // __FEMONTOOLS_H