vdr-plugin-femon/femonh264.c

487 lines
18 KiB
C
Raw Normal View History

/*
* Frontend Status Monitor plugin for the Video Disk Recorder
*
* See the README file for copyright information and how to reach the author.
*
* The original NAL SPS parsing and bitstream functions are taken from
2009-08-25 19:22:06 +02:00
* vdr-xineliboutput plugin by Petri Hintukainen.
*/
#include "femontools.h"
#include "femonh264.h"
2009-08-26 15:24:49 +02:00
const eVideoAspectRatio cFemonH264::s_AspectRatios[] =
{
VIDEO_ASPECT_RATIO_INVALID,
VIDEO_ASPECT_RATIO_1_1,
VIDEO_ASPECT_RATIO_12_11,
VIDEO_ASPECT_RATIO_10_11,
VIDEO_ASPECT_RATIO_16_11,
VIDEO_ASPECT_RATIO_40_33,
VIDEO_ASPECT_RATIO_24_11,
VIDEO_ASPECT_RATIO_20_11,
VIDEO_ASPECT_RATIO_32_11,
VIDEO_ASPECT_RATIO_80_33,
VIDEO_ASPECT_RATIO_18_11,
VIDEO_ASPECT_RATIO_15_11,
VIDEO_ASPECT_RATIO_64_33,
VIDEO_ASPECT_RATIO_160_99,
VIDEO_ASPECT_RATIO_4_3,
VIDEO_ASPECT_RATIO_3_2,
VIDEO_ASPECT_RATIO_2_1
};
2009-08-26 15:24:49 +02:00
const eVideoFormat cFemonH264::s_VideoFormats[] =
{
VIDEO_FORMAT_COMPONENT,
VIDEO_FORMAT_PAL,
VIDEO_FORMAT_NTSC,
VIDEO_FORMAT_SECAM,
VIDEO_FORMAT_MAC,
VIDEO_FORMAT_UNKNOWN,
VIDEO_FORMAT_RESERVED
};
2009-08-28 19:54:34 +02:00
const uint8_t cFemonH264::s_SeiNumClockTsTable[9] =
{
1, 1, 1, 2, 2, 3, 3, 2, 3
};
2009-08-26 15:24:49 +02:00
cFemonH264::cFemonH264(cFemonVideoIf *videohandler)
2009-08-28 19:54:34 +02:00
: 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)
2009-08-26 15:24:49 +02:00
{
}
2009-08-26 15:24:49 +02:00
cFemonH264::~cFemonH264()
{
2009-08-26 15:24:49 +02:00
}
bool cFemonH264::processVideo(const uint8_t *buf, int len)
{
2009-08-28 19:54:34 +02:00
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;
2009-08-26 15:24:49 +02:00
if (!m_VideoHandler)
return false;
// skip PES header
if (!PesLongEnough(len))
return false;
buf += PesPayloadOffset(buf);
2009-08-28 19:54:34 +02:00
start = buf;
2009-08-26 15:24:49 +02:00
for (;;) {
int consumed = 0;
buf = nextStartCode(buf, end);
if (buf >= end)
break;
switch (buf[3] & 0x1F) {
case NAL_AUD:
if (!aud_found) {
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);
aud_found = true;
break;
case 1: case 4: case 6: // P_FRAME;
case 2: case 7: // B_FRAME;
default: // NO_PICTURE;
break;
}
}
break;
case NAL_SPS:
if (!sps_found) {
//Dprintf("H.264: Found NAL SPS at offset %d/%d", buf - start, len);
2009-08-26 21:44:49 +02:00
int nal_len = nalUnescape(nal_data, buf + 4, int(end - buf - 4));
2009-08-26 15:24:49 +02:00
consumed = parseSPS(nal_data, nal_len);
if (consumed > 0)
sps_found = true;
}
break;
2009-08-28 19:54:34 +02:00
#if 0
2009-08-26 15:24:49 +02:00
case NAL_SEI:
if (!sei_found) {
//Dprintf("H.264: Found NAL SEI at offset %d/%d", buf - start, len);
2009-08-26 21:44:49 +02:00
int nal_len = nalUnescape(nal_data, buf + 4, int(end - buf - 4));
2009-08-26 15:24:49 +02:00
consumed = parseSEI(nal_data, nal_len);
if (consumed > 0)
sei_found = true;
}
break;
2009-08-28 19:54:34 +02:00
#endif
2009-08-26 15:24:49 +02:00
default:
break;
}
if (aud_found && sps_found && sei_found)
break;
buf += consumed + 4;
}
2009-08-28 19:54:34 +02:00
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);
}
}
2009-08-26 15:24:49 +02:00
return aud_found;
}
const uint8_t *cFemonH264::nextStartCode(const uint8_t *start, const uint8_t *end)
{
for (end -= 3; start < end; ++start) {
if ((start[0] == 0x00) && (start[1] == 0x00) && (start[2] == 0x01))
return start;
}
return (end + 3);
}
int cFemonH264::nalUnescape(uint8_t *dst, const uint8_t *src, int len)
{
int s = 0, d = 0;
while (s < len) {
if (!src[s] && !src[s + 1]) {
// hit 00 00 xx
dst[d] = dst[d + 1] = 0;
s += 2;
d += 2;
if (src[s] == 3) {
s++; // 00 00 03 xx --> 00 00 xx
if (s >= len)
return d;
}
}
dst[d++] = src[s++];
}
return d;
}
int cFemonH264::parseSPS(const uint8_t *buf, int len)
{
int profile_idc, pic_order_cnt_type, frame_mbs_only, i, j;
2009-08-25 19:22:06 +02:00
cBitStream bs(buf, len);
2009-08-28 19:54:34 +02:00
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;
2009-08-25 19:22:06 +02:00
profile_idc = bs.getU8();
//Dprintf("H.264 SPS: profile_idc %d", profile_idc);
2009-08-25 19:22:06 +02:00
bs.skipBits(16);
bs.skipUeGolomb(); // seq_parameter_set_id
if (profile_idc >= 100) {
2009-08-25 19:22:06 +02:00
if (bs.getUeGolomb() == 3) // chroma_format_idc
bs.skipBit(); // residual_colour_transform_flag
bs.skipUeGolomb(); // bit_depth_luma - 8
bs.skipUeGolomb(); // bit_depth_chroma - 8
bs.skipBit(); // transform_bypass
if (bs.getBit()) { // seq_scaling_matrix_present
for (i = 0; i < 8; i++) {
if (bs.getBit()) { // seq_scaling_list_present
int last = 8, next = 8, size = (i < 6) ? 16 : 64;
for (j = 0; j < size; j++) {
if (next)
2009-08-25 19:22:06 +02:00
next = (last + bs.getSeGolomb()) & 0xff;
last = next ?: last;
}
}
}
}
}
2009-08-25 19:22:06 +02:00
bs.skipUeGolomb(); // log2_max_frame_num - 4
pic_order_cnt_type = bs.getUeGolomb();
if (pic_order_cnt_type == 0)
2009-08-25 19:22:06 +02:00
bs.skipUeGolomb(); // log2_max_poc_lsb - 4
else if (pic_order_cnt_type == 1) {
2009-08-25 19:22:06 +02:00
bs.skipBit(); // delta_pic_order_always_zero
bs.skipSeGolomb(); // offset_for_non_ref_pic
bs.skipSeGolomb(); // offset_for_top_to_bottom_field
j = bs.getUeGolomb(); // num_ref_frames_in_pic_order_cnt_cycle
for (i = 0; i < j; i++)
2009-08-25 19:22:06 +02:00
bs.skipSeGolomb(); // offset_for_ref_frame[i]
}
2009-08-25 19:22:06 +02:00
bs.skipUeGolomb(); // ref_frames
bs.skipBit(); // gaps_in_frame_num_allowed
2009-08-26 15:24:49 +02:00
width = bs.getUeGolomb() + 1; // mbs
height = bs.getUeGolomb() + 1; // mbs
frame_mbs_only = bs.getBit();
2009-08-26 15:24:49 +02:00
//Dprintf("H.264 SPS: pic_width: %u mbs", width);
//Dprintf("H.264 SPS: pic_height: %u mbs", height);
//Dprintf("H.264 SPS: frame only flag: %d", frame_mbs_only);
2009-08-26 15:24:49 +02:00
width *= 16;
height *= 16 * (2 - frame_mbs_only);
if (!frame_mbs_only) {
2009-08-25 19:22:06 +02:00
if (bs.getBit()) { // mb_adaptive_frame_field_flag
//Dprintf("H.264 SPS: MBAFF");
}
}
2009-08-25 19:22:06 +02:00
bs.skipBit(); // direct_8x8_inference_flag
if (bs.getBit()) { // frame_cropping_flag
uint32_t crop_left = bs.getUeGolomb();
uint32_t crop_right = bs.getUeGolomb();
uint32_t crop_top = bs.getUeGolomb();
uint32_t crop_bottom = bs.getUeGolomb();
//Dprintf("H.264 SPS: cropping %d %d %d %d", crop_left, crop_top, crop_right, crop_bottom);
2009-08-26 15:24:49 +02:00
width -= 2 * (crop_left + crop_right);
if (frame_mbs_only)
2009-08-26 15:24:49 +02:00
height -= 2 * (crop_top + crop_bottom);
else
2009-08-26 15:24:49 +02:00
height -= 4 * (crop_top + crop_bottom);
}
// VUI parameters
2009-08-25 19:22:06 +02:00
if (bs.getBit()) { // vui_parameters_present_flag
if (bs.getBit()) { // aspect_ratio_info_present
uint32_t aspect_ratio_idc = bs.getU8();
//Dprintf("H.264 SPS: aspect_ratio_idc %d", aspect_ratio_idc);
2009-08-25 19:22:06 +02:00
if (aspect_ratio_idc == 255) { // extended sar
bs.skipBit(); // sar_width
bs.skipBit(); // sar_height
2009-08-26 15:24:49 +02:00
aspect_ratio = VIDEO_ASPECT_RATIO_EXTENDED;
2008-11-29 18:33:29 +01:00
//Dprintf("H.264 SPS: aspect ratio extended");
}
2009-08-26 15:24:49 +02:00
else if (aspect_ratio_idc < sizeof(s_AspectRatios) / sizeof(s_AspectRatios[0])) {
aspect_ratio = s_AspectRatios[aspect_ratio_idc];
//Dprintf("H.264 SPS: -> aspect ratio %d", aspect_ratio);
}
}
2009-08-25 19:22:06 +02:00
if (bs.getBit()) // overscan_info_present_flag
bs.skipBit(); // overscan_approriate_flag
if (bs.getBit()) { // video_signal_type_present_flag
2009-08-28 19:54:34 +02:00
uint32_t video_format;
video_format = bs.getBits(3); // video_format
2009-08-26 15:24:49 +02:00
if (video_format < sizeof(s_VideoFormats) / sizeof(s_VideoFormats[0])) {
format = s_VideoFormats[video_format];
//Dprintf("H.264 SPS: -> video format %d", format);
}
2009-08-28 19:54:34 +02:00
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
}
}
2009-08-28 19:54:34 +02:00
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;
2009-08-25 19:22:06 +02:00
2009-08-26 15:24:49 +02:00
return (bs.getIndex() / 8);
}
2009-08-26 15:24:49 +02:00
int cFemonH264::parseSEI(const uint8_t *buf, int len)
{
2008-11-29 18:33:29 +01:00
int num_referenced_subseqs, i;
2009-08-25 19:22:06 +02:00
cBitStream bs(buf, len);
2009-08-28 19:54:34 +02:00
double frame_rate = m_FrameRate;
double bit_rate = m_BitRate;
eVideoScan scan = m_Scan;
2009-08-26 15:24:49 +02:00
while ((bs.getIndex() * 8 + 16) < len) { // sei_message
2008-11-29 18:33:29 +01:00
int lastByte, payloadSize = 0, payloadType = 0;
// last_payload_type_byte
do {
2009-08-25 19:22:06 +02:00
lastByte = bs.getU8() & 0xFF;
2008-11-29 18:33:29 +01:00
payloadType += lastByte;
} while (lastByte == 0xFF);
// last_payload_size_byte
do {
2009-08-25 19:22:06 +02:00
lastByte = bs.getU8() & 0xFF;
2008-11-29 18:33:29 +01:00
payloadSize += lastByte;
} while (lastByte == 0xFF);
2009-08-26 15:24:49 +02:00
switch (payloadType) { // sei_payload
2009-08-28 19:54:34 +02:00
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;
2008-11-29 18:33:29 +01:00
2009-08-25 19:22:06 +02:00
case 12: // sub_seq_characteristics
bs.skipUeGolomb(); // sub_seq_layer_num
bs.skipUeGolomb(); // sub_seq_id
if (bs.getBit()) // duration_flag
bs.skipBits(32); // sub_seq_duration
if (bs.getBit()) { // average_rate_flag
bs.skipBit(); // accurate_statistics_flag
2009-08-28 19:54:34 +02:00
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);
2008-11-29 18:33:29 +01:00
}
2009-08-25 19:22:06 +02:00
num_referenced_subseqs = bs.getUeGolomb(); // num_referenced_subseqs
2008-11-29 18:33:29 +01:00
for (i = 0; i < num_referenced_subseqs; ++i) {
2009-08-25 19:22:06 +02:00
bs.skipUeGolomb(); // ref_sub_seq_layer_num
bs.skipUeGolomb(); // ref_sub_seq_id
bs.getBit(); // ref_sub_seq_direction
2008-11-29 18:33:29 +01:00
}
break;
default:
2009-08-26 15:24:49 +02:00
bs.skipBits(payloadSize * 8);
2008-11-29 18:33:29 +01:00
break;
}
// force byte align
2009-08-25 19:22:06 +02:00
bs.byteAlign();
2008-11-29 18:33:29 +01:00
}
2009-08-28 19:54:34 +02:00
m_FrameRate = frame_rate;
m_BitRate = bit_rate;
m_Scan = scan;
2009-08-26 15:24:49 +02:00
return (bs.getIndex() / 8);
}