2008-11-09 12:43:27 +01:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
* vdr-xineliboutput plugin by Petri Hintukainen.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "femontools.h"
|
|
|
|
#include "femonh264.h"
|
|
|
|
|
|
|
|
#define NAL_SEI 0x06 // Supplemental Enhancement Information
|
|
|
|
#define NAL_SPS 0x07 // Sequence Parameter Set
|
|
|
|
#define NAL_AUD 0x09 // Access Unit Delimiter
|
2009-06-18 11:17:42 +02:00
|
|
|
#define NAL_END_SEQ 0x0A // End of Sequence
|
2008-11-09 12:43:27 +01:00
|
|
|
|
|
|
|
#define IS_NAL_SEI(buf) (((buf)[0] == 0x00) && ((buf)[1] == 0x00) && ((buf)[2] == 0x01) && ((buf)[3] == NAL_SEI))
|
|
|
|
#define IS_NAL_SPS(buf) (((buf)[0] == 0x00) && ((buf)[1] == 0x00) && ((buf)[2] == 0x01) && ((buf)[3] == NAL_SPS))
|
|
|
|
#define IS_NAL_AUD(buf) (((buf)[0] == 0x00) && ((buf)[1] == 0x00) && ((buf)[2] == 0x01) && ((buf)[3] == NAL_AUD))
|
|
|
|
#define IS_NAL_END_SEQ(buf) (((buf)[0] == 0x00) && ((buf)[1] == 0x00) && ((buf)[2] == 0x01) && ((buf)[3] == NAL_END_SEQ))
|
|
|
|
|
|
|
|
// Picture types
|
|
|
|
#define NO_PICTURE 0
|
|
|
|
#define I_FRAME 1
|
|
|
|
#define P_FRAME 2
|
|
|
|
#define B_FRAME 3
|
|
|
|
|
|
|
|
static const eVideoAspectRatio aspect_ratios[] =
|
|
|
|
{
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
static const eVideoFormat video_formats[] =
|
|
|
|
{
|
|
|
|
VIDEO_FORMAT_COMPONENT,
|
|
|
|
VIDEO_FORMAT_PAL,
|
|
|
|
VIDEO_FORMAT_NTSC,
|
|
|
|
VIDEO_FORMAT_SECAM,
|
|
|
|
VIDEO_FORMAT_MAC,
|
|
|
|
VIDEO_FORMAT_UNKNOWN,
|
|
|
|
VIDEO_FORMAT_RESERVED
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int width;
|
|
|
|
int height;
|
|
|
|
eVideoAspectRatio aspect_ratio;
|
|
|
|
eVideoFormat format;
|
|
|
|
} h264_sps_data_t;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
double frame_rate;
|
|
|
|
double bitrate;
|
|
|
|
eVideoScan scan;
|
|
|
|
} h264_sei_data_t;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
const uint8_t *data;
|
|
|
|
int count; // bits
|
|
|
|
int index; // bits
|
|
|
|
} br_state;
|
|
|
|
|
|
|
|
#define BR_INIT(data, bytes) { (data), 8 * (bytes), 0 }
|
|
|
|
#define BR_EOF(br) ((br)->index >= (br)->count)
|
|
|
|
#define br_skip_bit(br) br_skip_bits(br,1)
|
|
|
|
#define br_get_u8(br) br_get_bits(br, 8)
|
|
|
|
#define br_get_u16(br) ((br_get_bits(br, 8) << 8) | br_get_bits(br, 8))
|
|
|
|
#define br_skip_ue_golomb(br) br_skip_golomb(br)
|
|
|
|
#define br_skip_se_golomb(br) br_skip_golomb(br)
|
|
|
|
|
|
|
|
static inline void br_init(br_state *br, const uint8_t *data, int bytes)
|
|
|
|
{
|
|
|
|
br->data = data;
|
|
|
|
br->count = 8 * bytes;
|
|
|
|
br->index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int br_get_bit(br_state *br)
|
|
|
|
{
|
|
|
|
if (br->index >= br->count)
|
|
|
|
return 1; // -> no infinite colomb's ...
|
|
|
|
|
|
|
|
int r = (br->data[br->index >> 3] >> (7 - (br->index & 7))) & 1;
|
|
|
|
br->index++;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline uint32_t br_get_bits(br_state *br, uint32_t n)
|
|
|
|
{
|
|
|
|
uint32_t r = 0;
|
|
|
|
while (n--)
|
|
|
|
r = r | (br_get_bit(br) << n);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void br_skip_bits(br_state *br, int n)
|
|
|
|
{
|
|
|
|
br->index += n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline uint32_t br_get_ue_golomb(br_state *br)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
while (!br_get_bit(br) && (n < 32))
|
|
|
|
n++;
|
|
|
|
return n ? ((1 << n) - 1) + br_get_bits(br, n) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int32_t br_get_se_golomb(br_state *br)
|
|
|
|
{
|
|
|
|
uint32_t r = br_get_ue_golomb(br) + 1;
|
|
|
|
return (r & 1) ? -(r >> 1) : (r >> 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void br_skip_golomb(br_state *br)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
while (!br_get_bit(br) && (n < 32))
|
|
|
|
n++;
|
|
|
|
br_skip_bits(br, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void br_byte_align(br_state *br)
|
|
|
|
{
|
|
|
|
int n = br->index % 8;
|
|
|
|
if (n > 0)
|
|
|
|
br_skip_bits(br, 8 - n);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool h264_parse_sps(const uint8_t *buf, int len, h264_sps_data_t *sps)
|
|
|
|
{
|
|
|
|
br_state br = BR_INIT(buf, len);
|
|
|
|
int profile_idc, pic_order_cnt_type;
|
|
|
|
int frame_mbs_only;
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
profile_idc = br_get_u8(&br);
|
|
|
|
|
|
|
|
//Dprintf("H.264 SPS: profile_idc %d", profile_idc);
|
|
|
|
|
|
|
|
br_skip_bits(&br, 16);
|
|
|
|
br_skip_ue_golomb(&br); // seq_parameter_set_id
|
|
|
|
if (profile_idc >= 100) {
|
|
|
|
if (br_get_ue_golomb(&br) == 3) // chroma_format_idc
|
|
|
|
br_skip_bit(&br); // residual_colour_transform_flag
|
|
|
|
br_skip_ue_golomb(&br); // bit_depth_luma - 8
|
|
|
|
br_skip_ue_golomb(&br); // bit_depth_chroma - 8
|
|
|
|
br_skip_bit(&br); // transform_bypass
|
|
|
|
if (br_get_bit(&br)) { // seq_scaling_matrix_present
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
if (br_get_bit(&br)) { // seq_scaling_list_present
|
|
|
|
int last = 8, next = 8, size = (i < 6) ? 16 : 64;
|
|
|
|
for (j = 0; j < size; j++) {
|
|
|
|
if (next)
|
|
|
|
next = (last + br_get_se_golomb(&br)) & 0xff;
|
|
|
|
last = next ?: last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
br_skip_ue_golomb(&br); // log2_max_frame_num - 4
|
|
|
|
pic_order_cnt_type = br_get_ue_golomb(&br);
|
|
|
|
if (pic_order_cnt_type == 0)
|
|
|
|
br_skip_ue_golomb(&br); // log2_max_poc_lsb - 4
|
|
|
|
else if (pic_order_cnt_type == 1) {
|
|
|
|
br_skip_bit(&br); // delta_pic_order_always_zero
|
|
|
|
br_skip_se_golomb(&br); // offset_for_non_ref_pic
|
|
|
|
br_skip_se_golomb(&br); // offset_for_top_to_bottom_field
|
|
|
|
j = br_get_ue_golomb(&br); // num_ref_frames_in_pic_order_cnt_cycle
|
|
|
|
for (i = 0; i < j; i++)
|
|
|
|
br_skip_se_golomb(&br); // offset_for_ref_frame[i]
|
|
|
|
}
|
|
|
|
br_skip_ue_golomb(&br); // ref_frames
|
|
|
|
br_skip_bit(&br); // gaps_in_frame_num_allowed
|
|
|
|
sps->width = br_get_ue_golomb(&br) + 1; // mbs
|
|
|
|
sps->height = br_get_ue_golomb(&br) + 1; // mbs
|
|
|
|
frame_mbs_only = br_get_bit(&br);
|
|
|
|
|
|
|
|
//Dprintf("H.264 SPS: pic_width: %u mbs", (unsigned int)sps->width);
|
|
|
|
//Dprintf("H.264 SPS: pic_height: %u mbs", (unsigned int)sps->height);
|
|
|
|
//Dprintf("H.264 SPS: frame only flag: %d", frame_mbs_only);
|
|
|
|
|
|
|
|
sps->width *= 16;
|
|
|
|
sps->height *= 16 * (2 - frame_mbs_only);
|
|
|
|
|
|
|
|
if (!frame_mbs_only) {
|
|
|
|
if (br_get_bit(&br)) { // mb_adaptive_frame_field_flag
|
|
|
|
//Dprintf("H.264 SPS: MBAFF");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
br_skip_bit(&br); // direct_8x8_inference_flag
|
|
|
|
if (br_get_bit(&br)) { // frame_cropping_flag
|
|
|
|
uint32_t crop_left = br_get_ue_golomb(&br);
|
|
|
|
uint32_t crop_right = br_get_ue_golomb(&br);
|
|
|
|
uint32_t crop_top = br_get_ue_golomb(&br);
|
|
|
|
uint32_t crop_bottom = br_get_ue_golomb(&br);
|
|
|
|
//Dprintf("H.264 SPS: cropping %d %d %d %d", crop_left, crop_top, crop_right, crop_bottom);
|
|
|
|
|
|
|
|
sps->width -= 2 * (crop_left + crop_right);
|
|
|
|
if (frame_mbs_only)
|
|
|
|
sps->height -= 2 * (crop_top + crop_bottom);
|
|
|
|
else
|
|
|
|
sps->height -= 4 * (crop_top + crop_bottom);
|
|
|
|
}
|
|
|
|
|
|
|
|
// VUI parameters
|
|
|
|
sps->aspect_ratio = VIDEO_ASPECT_RATIO_INVALID;
|
|
|
|
sps->format = VIDEO_FORMAT_INVALID;
|
|
|
|
if (br_get_bit(&br)) { // vui_parameters_present_flag
|
|
|
|
if (br_get_bit(&br)) { // aspect_ratio_info_present
|
|
|
|
uint32_t aspect_ratio_idc = br_get_u8(&br);
|
|
|
|
//Dprintf("H.264 SPS: aspect_ratio_idc %d", aspect_ratio_idc);
|
|
|
|
if (aspect_ratio_idc == 255) { // extended sar
|
|
|
|
br_skip_bit(&br); // sar_width
|
|
|
|
br_skip_bit(&br); // sar_height
|
|
|
|
sps->aspect_ratio = VIDEO_ASPECT_RATIO_EXTENDED;
|
2008-11-29 18:33:29 +01:00
|
|
|
//Dprintf("H.264 SPS: aspect ratio extended");
|
2008-11-09 12:43:27 +01:00
|
|
|
}
|
|
|
|
else if (aspect_ratio_idc < sizeof(aspect_ratios) / sizeof(aspect_ratios[0])) {
|
|
|
|
sps->aspect_ratio = aspect_ratios[aspect_ratio_idc];
|
2008-11-29 18:33:29 +01:00
|
|
|
//Dprintf("H.264 SPS: -> aspect ratio %d", sps->aspect_ratio);
|
2008-11-09 12:43:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (br_get_bit(&br)) // overscan_info_present_flag
|
|
|
|
br_skip_bit(&br); // overscan_approriate_flag
|
|
|
|
if (br_get_bit(&br)) { // video_signal_type_present_flag
|
|
|
|
uint32_t video_format = br_get_bits(&br, 3);
|
|
|
|
if (video_format < sizeof(video_formats) / sizeof(video_formats[0])) {
|
|
|
|
sps->format = video_formats[video_format];
|
|
|
|
//Dprintf("H.264 SPS: -> video format %d", sps->format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-29 18:33:29 +01:00
|
|
|
//Dprintf("H.264 SPS: -> video size %dx%d, aspect %d", sps->width, sps->height, sps->aspect_ratio);
|
2008-11-09 12:43:27 +01:00
|
|
|
|
|
|
|
if (BR_EOF(&br)) {
|
|
|
|
//Dprintf("H.264 SPS: not enough data ?");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool h264_parse_sei(const uint8_t *buf, int len, h264_sei_data_t *sei)
|
|
|
|
{
|
2008-11-29 18:33:29 +01:00
|
|
|
int num_referenced_subseqs, i;
|
2008-11-09 12:43:27 +01:00
|
|
|
br_state br = BR_INIT(buf, len);
|
|
|
|
|
2008-11-29 18:33:29 +01:00
|
|
|
while (!BR_EOF(&br)) { // sei_message
|
|
|
|
int lastByte, payloadSize = 0, payloadType = 0;
|
|
|
|
|
|
|
|
// last_payload_type_byte
|
|
|
|
do {
|
|
|
|
lastByte = br_get_u8(&br) & 0xFF;
|
|
|
|
payloadType += lastByte;
|
|
|
|
} while (lastByte == 0xFF);
|
|
|
|
|
|
|
|
// last_payload_size_byte
|
|
|
|
do {
|
|
|
|
lastByte = br_get_u8(&br) & 0xFF;
|
|
|
|
payloadSize += lastByte;
|
|
|
|
} while (lastByte == 0xFF);
|
|
|
|
|
|
|
|
switch (payloadType) { // sei_payload
|
|
|
|
//case 1: // pic_timing
|
|
|
|
// ...
|
|
|
|
// switch (br_get_bits(&br, 2)) { // ct_type
|
|
|
|
// case 0:
|
|
|
|
// sei->scan = VIDEO_SCAN_PROGRESSIVE;
|
|
|
|
// break;
|
|
|
|
// case 1:
|
|
|
|
// sei->scan = VIDEO_SCAN_INTERLACED;
|
|
|
|
// break;
|
|
|
|
// case 2:
|
|
|
|
// sei->scan = VIDEO_SCAN_UNKNOWN;
|
|
|
|
// break;
|
|
|
|
// default:
|
|
|
|
// sei->scan = VIDEO_SCAN_RESERVED;
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// break;
|
|
|
|
|
|
|
|
case 12: // sub_seq_characteristics
|
|
|
|
br_skip_ue_golomb(&br); // sub_seq_layer_num
|
|
|
|
br_skip_ue_golomb(&br); // sub_seq_id
|
|
|
|
if (br_get_bit(&br)) // duration_flag
|
|
|
|
br_skip_bits(&br, 32); // sub_seq_duration
|
|
|
|
if (br_get_bit(&br)) { // average_rate_flag
|
|
|
|
br_skip_bit(&br); // accurate_statistics_flag
|
|
|
|
sei->bitrate = br_get_u16(&br); // average_bit_rate
|
|
|
|
sei->frame_rate = br_get_u16(&br); // average_frame_rate
|
|
|
|
//Dprintf("H.264 SEI: -> stream bitrate %.1f, frame rate %.1f", sei->bitrate, sei->frame_rate);
|
|
|
|
}
|
|
|
|
num_referenced_subseqs = br_get_ue_golomb(&br); // num_referenced_subseqs
|
|
|
|
for (i = 0; i < num_referenced_subseqs; ++i) {
|
|
|
|
br_skip_ue_golomb(&br); // ref_sub_seq_layer_num
|
|
|
|
br_skip_ue_golomb(&br); // ref_sub_seq_id
|
|
|
|
br_get_bit(&br); // ref_sub_seq_direction
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
br_skip_bits(&br, payloadSize);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// force byte align
|
|
|
|
br_byte_align(&br);
|
|
|
|
}
|
2008-11-09 12:43:27 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int h264_nal_unescape(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int h264_get_picture_type(const uint8_t *buf, int len)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < (len - 5); ++i) {
|
|
|
|
if (buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1 && buf[i + 3] == NAL_AUD) {
|
2009-06-18 11:17:42 +02:00
|
|
|
uint8_t type = (uint8_t)(buf[i + 4] >> 5);
|
2008-11-09 12:43:27 +01:00
|
|
|
switch (type) {
|
|
|
|
case 0: case 3: case 5: return I_FRAME;
|
|
|
|
case 1: case 4: case 6: return P_FRAME;
|
|
|
|
case 2: case 7: return B_FRAME;
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NO_PICTURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool getH264VideoInfo(uint8_t *buf, int len, video_info_t *info)
|
|
|
|
{
|
2008-11-29 18:33:29 +01:00
|
|
|
bool sps_found = false, sei_found = true; // sei currently disabled
|
|
|
|
|
2008-11-09 12:43:27 +01:00
|
|
|
// H.264 detection, search for NAL AUD
|
|
|
|
if (!IS_NAL_AUD(buf))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// If I-frame, search for NAL SPS
|
|
|
|
if (h264_get_picture_type(buf, len) != I_FRAME)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
info->codec = VIDEO_CODEC_H264;
|
|
|
|
|
|
|
|
// Scan video packet ...
|
|
|
|
for (int i = 5; i < len - 4; i++) {
|
|
|
|
// ... for sequence parameter set
|
2008-11-29 18:33:29 +01:00
|
|
|
if (!sps_found && (buf[i] == 0x00) && (buf[i + 1] == 0x00) && (buf[i + 2] == 0x01) && (buf[i + 3] & 0x1f) == NAL_SPS) {
|
2008-11-09 12:43:27 +01:00
|
|
|
uint8_t nal_data[len];
|
|
|
|
int nal_len;
|
|
|
|
//Dprintf("H.264: Found NAL SPS at offset %d/%d", i, len);
|
|
|
|
if (0 < (nal_len = h264_nal_unescape(nal_data, buf + i + 4, len - i - 4))) {
|
2009-06-18 11:17:42 +02:00
|
|
|
h264_sps_data_t sps = { 0, 0, VIDEO_ASPECT_RATIO_INVALID, VIDEO_FORMAT_INVALID };
|
2008-11-09 12:43:27 +01:00
|
|
|
if (h264_parse_sps(nal_data, nal_len, &sps)) {
|
|
|
|
info->format = sps.format;
|
|
|
|
info->width = sps.width;
|
|
|
|
info->height = sps.height;
|
|
|
|
info->aspectRatio = sps.aspect_ratio;
|
2008-11-29 18:33:29 +01:00
|
|
|
sps_found = true;
|
2008-11-09 12:43:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ... for supplemental enhancement information
|
2008-11-29 18:33:29 +01:00
|
|
|
if (!sei_found && (buf[i] == 0x00) && (buf[i + 1] == 0x00) && (buf[i + 2] == 0x01) && (buf[i + 3] & 0x1f) == NAL_SEI) {
|
2008-11-09 12:43:27 +01:00
|
|
|
uint8_t nal_data[len];
|
|
|
|
int nal_len;
|
|
|
|
//Dprintf("H.264: Found NAL SEI at offset %d/%d", i, len);
|
|
|
|
if (0 < (nal_len = h264_nal_unescape(nal_data, buf + i + 4, len - i - 4))) {
|
2009-06-18 11:17:42 +02:00
|
|
|
h264_sei_data_t sei = { 0, 0, VIDEO_SCAN_INVALID };
|
2008-11-09 12:43:27 +01:00
|
|
|
if (h264_parse_sei(nal_data, nal_len, &sei)) {
|
|
|
|
info->frameRate = sei.frame_rate;
|
|
|
|
info->bitrate = sei.bitrate;
|
|
|
|
info->scan = sei.scan;
|
2008-11-29 18:33:29 +01:00
|
|
|
sei_found = true;
|
2008-11-09 12:43:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-11-29 18:33:29 +01:00
|
|
|
if (sps_found && sei_found)
|
|
|
|
break;
|
2008-11-09 12:43:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|