mirror of
https://github.com/libretro/RetroArch
synced 2025-02-02 05:54:16 +00:00
803 lines
28 KiB
C
803 lines
28 KiB
C
/*
|
|
Copyright (c) 2012, Broadcom Europe Ltd
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "containers/containers.h"
|
|
|
|
#include "containers/core/containers_logging.h"
|
|
#include "containers/core/containers_list.h"
|
|
#include "containers/core/containers_bits.h"
|
|
#include "rtp_priv.h"
|
|
#include "rtp_base64.h"
|
|
#include "rtp_h264.h"
|
|
|
|
/******************************************************************************
|
|
Defines and constants.
|
|
******************************************************************************/
|
|
|
|
/** H.264 payload flag bits */
|
|
typedef enum
|
|
{
|
|
H264F_NEXT_PACKET_IS_START = 0,
|
|
H264F_INSIDE_FRAGMENT,
|
|
H264F_OUTPUT_NAL_HEADER,
|
|
} h264_flag_bit_t;
|
|
|
|
/** Bit mask to extract F zero bit from NAL unit header */
|
|
#define NAL_UNIT_FZERO_MASK 0x80
|
|
/** Bit mask to extract NAL unit type from NAL unit header */
|
|
#define NAL_UNIT_TYPE_MASK 0x1F
|
|
|
|
/** NAL unit type codes */
|
|
enum
|
|
{
|
|
/* 0 unspecified */
|
|
NAL_UNIT_NON_IDR = 1,
|
|
NAL_UNIT_PARTITION_A = 2,
|
|
NAL_UNIT_PARTITION_B = 3,
|
|
NAL_UNIT_PARTITION_C = 4,
|
|
NAL_UNIT_IDR = 5,
|
|
NAL_UNIT_SEI = 6,
|
|
NAL_UNIT_SEQUENCE_PARAMETER_SET = 7,
|
|
NAL_UNIT_PICTURE_PARAMETER_SET = 8,
|
|
NAL_UNIT_ACCESS_UNIT_DELIMITER = 9,
|
|
NAL_UNIT_END_OF_SEQUENCE = 10,
|
|
NAL_UNIT_END_OF_STREAM = 11,
|
|
NAL_UNIT_FILLER = 12,
|
|
NAL_UNIT_EXT_SEQUENCE_PARAMETER_SET = 13,
|
|
NAL_UNIT_PREFIX = 14,
|
|
NAL_UNIT_SUBSET_SEQUENCE_PARAMETER_SET = 15,
|
|
/* 16 to 18 reserved */
|
|
NAL_UNIT_AUXILIARY = 19,
|
|
NAL_UNIT_EXTENSION = 20,
|
|
/* 21 to 23 reserved */
|
|
NAL_UNIT_STAP_A = 24,
|
|
NAL_UNIT_STAP_B = 25,
|
|
NAL_UNIT_MTAP16 = 26,
|
|
NAL_UNIT_MTAP24 = 27,
|
|
NAL_UNIT_FU_A = 28,
|
|
NAL_UNIT_FU_B = 29,
|
|
/* 30 to 31 unspecified */
|
|
};
|
|
|
|
/** Fragment unit header indicator bits */
|
|
typedef enum
|
|
{
|
|
FRAGMENT_UNIT_HEADER_RESERVED = 5,
|
|
FRAGMENT_UNIT_HEADER_END = 6,
|
|
FRAGMENT_UNIT_HEADER_START = 7,
|
|
} fragment_unit_header_bit_t;
|
|
|
|
#define MACROBLOCK_WIDTH 16
|
|
#define MACROBLOCK_HEIGHT 16
|
|
|
|
/** H.264 RTP timestamp clock rate */
|
|
#define H264_TIMESTAMP_CLOCK 90000
|
|
|
|
typedef enum
|
|
{
|
|
CHROMA_FORMAT_MONO = 0,
|
|
CHROMA_FORMAT_YUV_420 = 1,
|
|
CHROMA_FORMAT_YUV_422 = 2,
|
|
CHROMA_FORMAT_YUV_444 = 3,
|
|
CHROMA_FORMAT_YUV_444_PLANAR = 4,
|
|
CHROMA_FORMAT_RGB = 5,
|
|
} CHROMA_FORMAT_T;
|
|
|
|
uint32_t chroma_sub_width[] = {
|
|
1, 2, 2, 1, 1, 1
|
|
};
|
|
|
|
uint32_t chroma_sub_height[] = {
|
|
1, 2, 1, 1, 1, 1
|
|
};
|
|
|
|
/******************************************************************************
|
|
Type definitions
|
|
******************************************************************************/
|
|
|
|
typedef struct h264_payload_tag
|
|
{
|
|
uint32_t nal_unit_size; /**< Number of NAL unit bytes left to write */
|
|
uint8_t flags; /**< H.264 payload flags */
|
|
uint8_t header_bytes_to_write; /**< Number of start code bytes left to write */
|
|
uint8_t nal_header; /**< Header for next NAL unit */
|
|
} H264_PAYLOAD_T;
|
|
|
|
/******************************************************************************
|
|
Function prototypes
|
|
******************************************************************************/
|
|
VC_CONTAINER_STATUS_T h264_parameter_handler(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track, const VC_CONTAINERS_LIST_T *params);
|
|
|
|
/******************************************************************************
|
|
Local Functions
|
|
******************************************************************************/
|
|
|
|
/**************************************************************************//**
|
|
* Remove emulation prevention bytes from a buffer.
|
|
* These are 0x03 bytes inserted to prevent misinterprentation of a byte
|
|
* sequence in a buffer as a start code.
|
|
*
|
|
* @param sprop The buffer from which bytes are to be removed.
|
|
* @param sprop_size The number of bytes in the buffer.
|
|
* @return The new number of bytes in the buffer.
|
|
*/
|
|
static uint32_t h264_remove_emulation_prevention_bytes(uint8_t *sprop,
|
|
uint32_t sprop_size)
|
|
{
|
|
uint32_t offset = 0;
|
|
uint8_t nal_unit_type = sprop[offset++];
|
|
uint32_t new_sprop_size = sprop_size;
|
|
uint8_t first_byte, second_byte;
|
|
|
|
nal_unit_type &= 0x1F; /* Just keep NAL unit type bits */
|
|
|
|
/* Certain NAL unit types need a byte triplet passed first */
|
|
if (nal_unit_type == NAL_UNIT_PREFIX || nal_unit_type == NAL_UNIT_EXTENSION)
|
|
offset += 3;
|
|
|
|
/* Make sure there is enough data for there to be a 0x00 0x00 0x03 sequence */
|
|
if (offset + 2 >= new_sprop_size)
|
|
return new_sprop_size;
|
|
|
|
/* Keep a rolling set of the last couple of bytes */
|
|
first_byte = sprop[offset++];
|
|
second_byte = sprop[offset++];
|
|
|
|
while (offset < new_sprop_size)
|
|
{
|
|
uint8_t next_byte = sprop[offset];
|
|
|
|
if (!first_byte && !second_byte && next_byte == 0x03)
|
|
{
|
|
/* Remove the emulation prevention byte (0x03) */
|
|
new_sprop_size--;
|
|
if (offset == new_sprop_size) /* No more data to check */
|
|
break;
|
|
memmove(&sprop[offset], &sprop[offset + 1], new_sprop_size - offset);
|
|
next_byte = sprop[offset];
|
|
} else
|
|
offset++;
|
|
|
|
first_byte = second_byte;
|
|
second_byte = next_byte;
|
|
}
|
|
|
|
return new_sprop_size;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Skip a scaling list in a bit stream.
|
|
*
|
|
* @param p_ctx The container context.
|
|
* @param sprop The bit stream containing the scaling list.
|
|
* @param size_of_scaling_list The size of the scaling list.
|
|
*/
|
|
static void h264_skip_scaling_list(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_BITS_T *sprop,
|
|
uint32_t size_of_scaling_list)
|
|
{
|
|
uint32_t last_scale = 8;
|
|
uint32_t next_scale = 8;
|
|
int32_t delta_scale;
|
|
uint32_t jj;
|
|
|
|
/* Algorithm taken from H.264 section 7.3.2.1.1.1 */
|
|
for (jj = 0; jj < size_of_scaling_list; jj++)
|
|
{
|
|
if (next_scale)
|
|
{
|
|
delta_scale = BITS_READ_S32_EXP(p_ctx, sprop, "delta_scale");
|
|
next_scale = (last_scale + delta_scale + 256) & 0xFF;
|
|
|
|
if (next_scale)
|
|
last_scale = next_scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Get the chroma format from the bit stream.
|
|
*
|
|
* @param p_ctx The container context.
|
|
* @param sprop The bit stream containing the scaling list.
|
|
* @return The chroma format index.
|
|
*/
|
|
static uint32_t h264_get_chroma_format(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_BITS_T *sprop)
|
|
{
|
|
uint32_t chroma_format_idc;
|
|
|
|
chroma_format_idc = BITS_READ_U32_EXP(p_ctx, sprop, "chroma_format_idc");
|
|
if (chroma_format_idc == 3 && BITS_READ_U32(p_ctx, sprop, 1, "separate_colour_plane_flag"))
|
|
chroma_format_idc = CHROMA_FORMAT_YUV_444_PLANAR;
|
|
|
|
BITS_SKIP_EXP(p_ctx, sprop, "bit_depth_luma_minus8");
|
|
BITS_SKIP_EXP(p_ctx, sprop, "bit_depth_chroma_minus8");
|
|
BITS_SKIP(p_ctx, sprop, 1, "qpprime_y_zero_transform_bypass_flag");
|
|
|
|
if (BITS_READ_U32(p_ctx, sprop, 1, "seq_scaling_matrix_present_flag"))
|
|
{
|
|
uint32_t scaling_lists = (chroma_format_idc == 3) ? 12 : 8;
|
|
uint32_t ii;
|
|
|
|
for (ii = 0; ii < scaling_lists; ii++)
|
|
{
|
|
if (BITS_READ_U32(p_ctx, sprop, 1, "seq_scaling_list_present_flag"))
|
|
h264_skip_scaling_list(p_ctx, sprop, (ii < 6) ? 16 : 64);
|
|
}
|
|
}
|
|
|
|
return chroma_format_idc;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Decode an H.264 sequence parameter set and update track information.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param track The track to be updated.
|
|
* @param sprop The bit stream containing the sequence parameter set.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_decode_sequence_parameter_set(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track,
|
|
VC_CONTAINER_BITS_T *sprop)
|
|
{
|
|
VC_CONTAINER_VIDEO_FORMAT_T *video = &track->format->type->video;
|
|
uint32_t pic_order_cnt_type, chroma_format_idc;
|
|
uint32_t pic_width_in_mbs_minus1, pic_height_in_map_units_minus1, frame_mbs_only_flag;
|
|
uint32_t frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset, frame_crop_bottom_offset;
|
|
uint8_t profile_idc;
|
|
|
|
/* This structure is defined by H.264 section 7.3.2.1.1 */
|
|
profile_idc = BITS_READ_U8(p_ctx, sprop, 8, "profile_idc");
|
|
BITS_SKIP(p_ctx, sprop, 16, "Rest of profile_level_id");
|
|
|
|
BITS_READ_U32_EXP(p_ctx, sprop, "seq_parameter_set_id");
|
|
|
|
chroma_format_idc = CHROMA_FORMAT_RGB;
|
|
if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 ||
|
|
profile_idc == 244 || profile_idc == 44 || profile_idc == 83 ||
|
|
profile_idc == 86 || profile_idc == 118 || profile_idc == 128)
|
|
{
|
|
chroma_format_idc = h264_get_chroma_format(p_ctx, sprop);
|
|
if (chroma_format_idc > CHROMA_FORMAT_YUV_444_PLANAR)
|
|
goto error;
|
|
}
|
|
|
|
BITS_SKIP_EXP(p_ctx, sprop, "log2_max_frame_num_minus4");
|
|
pic_order_cnt_type = BITS_READ_U32_EXP(p_ctx, sprop, "pic_order_cnt_type");
|
|
if (pic_order_cnt_type == 0)
|
|
{
|
|
BITS_SKIP_EXP(p_ctx, sprop, "log2_max_pic_order_cnt_lsb_minus4");
|
|
}
|
|
else if (pic_order_cnt_type == 1)
|
|
{
|
|
uint32_t num_ref_frames_in_pic_order_cnt_cycle;
|
|
uint32_t ii;
|
|
|
|
BITS_SKIP(p_ctx, sprop, 1, "delta_pic_order_always_zero_flag");
|
|
BITS_SKIP_EXP(p_ctx, sprop, "offset_for_non_ref_pic");
|
|
BITS_SKIP_EXP(p_ctx, sprop, "offset_for_top_to_bottom_field");
|
|
num_ref_frames_in_pic_order_cnt_cycle = BITS_READ_U32_EXP(p_ctx, sprop, "num_ref_frames_in_pic_order_cnt_cycle");
|
|
|
|
for (ii = 0; ii < num_ref_frames_in_pic_order_cnt_cycle; ii++)
|
|
BITS_SKIP_EXP(p_ctx, sprop, "offset_for_ref_frame");
|
|
}
|
|
|
|
BITS_SKIP_EXP(p_ctx, sprop, "max_num_ref_frames");
|
|
BITS_SKIP(p_ctx, sprop, 1, "gaps_in_frame_num_value_allowed_flag");
|
|
|
|
pic_width_in_mbs_minus1 = BITS_READ_U32_EXP(p_ctx, sprop, "pic_width_in_mbs_minus1");
|
|
pic_height_in_map_units_minus1 = BITS_READ_U32_EXP(p_ctx, sprop, "pic_height_in_map_units_minus1");
|
|
frame_mbs_only_flag = BITS_READ_U32(p_ctx, sprop, 1, "frame_mbs_only_flag");
|
|
|
|
/* Can now set the overall width and height in pixels */
|
|
video->width = (pic_width_in_mbs_minus1 + 1) * MACROBLOCK_WIDTH;
|
|
video->height = (2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * MACROBLOCK_HEIGHT;
|
|
|
|
if (!frame_mbs_only_flag)
|
|
BITS_SKIP(p_ctx, sprop, 1, "mb_adaptive_frame_field_flag");
|
|
BITS_SKIP(p_ctx, sprop, 1, "direct_8x8_inference_flag");
|
|
|
|
if (BITS_READ_U32(p_ctx, sprop, 1, "frame_cropping_flag"))
|
|
{
|
|
/* Visible area is restricted */
|
|
frame_crop_left_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_left_offset");
|
|
frame_crop_right_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_right_offset");
|
|
frame_crop_top_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_top_offset");
|
|
frame_crop_bottom_offset = BITS_READ_U32_EXP(p_ctx, sprop, "frame_crop_bottom_offset");
|
|
|
|
/* Need to adjust offsets for 4:2:0 and 4:2:2 chroma formats and field/frame flag */
|
|
frame_crop_left_offset *= chroma_sub_width[chroma_format_idc];
|
|
frame_crop_right_offset *= chroma_sub_width[chroma_format_idc];
|
|
frame_crop_top_offset *= chroma_sub_height[chroma_format_idc] * (2 - frame_mbs_only_flag);
|
|
frame_crop_bottom_offset *= chroma_sub_height[chroma_format_idc] * (2 - frame_mbs_only_flag);
|
|
|
|
if ((frame_crop_left_offset + frame_crop_right_offset) >= video->width ||
|
|
(frame_crop_top_offset + frame_crop_bottom_offset) >= video->height)
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: frame crop offsets (%u, %u, %u, %u) larger than frame (%u, %u)",
|
|
frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset,
|
|
frame_crop_bottom_offset, video->width, video->height);
|
|
goto error;
|
|
}
|
|
|
|
video->x_offset = frame_crop_left_offset;
|
|
video->y_offset = frame_crop_top_offset;
|
|
video->visible_width = video->width - frame_crop_left_offset - frame_crop_right_offset;
|
|
video->visible_height = video->height - frame_crop_top_offset - frame_crop_bottom_offset;
|
|
} else {
|
|
video->visible_width = video->width;
|
|
video->visible_height = video->height;
|
|
}
|
|
|
|
/* vui_parameters may follow, but these will not be decoded */
|
|
|
|
if (!BITS_VALID(p_ctx, sprop))
|
|
goto error;
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
|
|
error:
|
|
LOG_ERROR(p_ctx, "H.264: sequence_parameter_set failed to decode");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Decode an H.264 sprop and update track information.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param track The track to be updated.
|
|
* @param sprop The bit stream containing the sprop.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_decode_sprop(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track,
|
|
VC_CONTAINER_BITS_T *sprop)
|
|
{
|
|
switch (BITS_READ_U32(p_ctx, sprop, 8, "nal_unit_header") & NAL_UNIT_TYPE_MASK)
|
|
{
|
|
case NAL_UNIT_SEQUENCE_PARAMETER_SET:
|
|
return h264_decode_sequence_parameter_set(p_ctx, track, sprop);
|
|
case NAL_UNIT_PICTURE_PARAMETER_SET:
|
|
/* Not handled, but valid */
|
|
return VC_CONTAINER_SUCCESS;
|
|
default:
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Decode the sprop parameter sets URI parameter and update track information.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param track The track to be updated.
|
|
* @param params The URI parameter list.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_get_sprop_parameter_sets(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track,
|
|
const VC_CONTAINERS_LIST_T *params)
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
PARAMETER_T param;
|
|
size_t str_len;
|
|
uint32_t extradata_size = 0;
|
|
uint8_t *sprop;
|
|
const char *set;
|
|
const char *comma;
|
|
|
|
/* Get the value of sprop-parameter-sets, base64 decode the (comma separated)
|
|
* sets, store all of them in track->priv->extradata and also decode to
|
|
* validate and fill in video format info. */
|
|
|
|
param.name = "sprop-parameter-sets";
|
|
if (!vc_containers_list_find_entry(params, ¶m) || !param.value)
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets is required, but not found");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
/* First pass, calculate total size of buffer needed */
|
|
set = param.value;
|
|
do {
|
|
comma = strchr(set, ',');
|
|
str_len = comma ? (size_t)(comma - set) : strlen(set);
|
|
/* Allow space for the NAL unit and a start code */
|
|
extradata_size += rtp_base64_byte_length(set, str_len) + 4;
|
|
set = comma + 1;
|
|
} while (comma);
|
|
|
|
if (!extradata_size)
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets doesn't contain useful data");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
status = vc_container_track_allocate_extradata(p_ctx, track, extradata_size);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
track->format->extradata_size = extradata_size;
|
|
sprop = track->priv->extradata;
|
|
|
|
/* Now decode the data into the buffer, and validate / use it to fill in format */
|
|
set = param.value;
|
|
do {
|
|
uint8_t *next_sprop;
|
|
uint32_t sprop_size;
|
|
VC_CONTAINER_BITS_T sprop_stream;
|
|
|
|
comma = strchr(set, ',');
|
|
str_len = comma ? (size_t)(comma - set) : strlen(set);
|
|
|
|
/* Insert a start code (0x00000001 in network order) */
|
|
*sprop++ = 0x00; *sprop++ = 0x00; *sprop++ = 0x00; *sprop++ = 0x01;
|
|
extradata_size -= 4;
|
|
|
|
next_sprop = rtp_base64_decode(set, str_len, sprop, extradata_size);
|
|
if (!next_sprop)
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: sprop-parameter-sets failed to decode");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
sprop_size = next_sprop - sprop;
|
|
if (sprop_size)
|
|
{
|
|
uint32_t new_sprop_size;
|
|
|
|
/* Need to remove emulation prevention bytes before decoding */
|
|
new_sprop_size = h264_remove_emulation_prevention_bytes(sprop, sprop_size);
|
|
|
|
BITS_INIT(p_ctx, &sprop_stream, sprop, new_sprop_size);
|
|
status = h264_decode_sprop(p_ctx, track, &sprop_stream);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
/* If necessary, decode sprop again, to put back the emulation prevention bytes */
|
|
if (new_sprop_size != sprop_size)
|
|
rtp_base64_decode(set, str_len, sprop, sprop_size);
|
|
|
|
extradata_size -= sprop_size;
|
|
sprop = next_sprop;
|
|
}
|
|
|
|
set = comma + 1;
|
|
} while (comma);
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Check URI parameter list for unsupported features.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param params The URI parameter list.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_check_unsupported_features(VC_CONTAINER_T *p_ctx,
|
|
const VC_CONTAINERS_LIST_T *params)
|
|
{
|
|
uint32_t u32_unused;
|
|
|
|
/* Limitation: interleaving not yet supported */
|
|
if (rtp_get_parameter_u32(params, "sprop-interleaving-depth", &u32_unused) ||
|
|
rtp_get_parameter_u32(params, "sprop-deint-buf-req", &u32_unused) ||
|
|
rtp_get_parameter_u32(params, "sprop-init-buf-time", &u32_unused) ||
|
|
rtp_get_parameter_u32(params, "sprop-max-don-diff", &u32_unused))
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: Interleaved packetization is not supported");
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Get and check the packetization mode URI parameter.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param params The URI parameter list.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_get_packetization_mode(VC_CONTAINER_T *p_ctx,
|
|
const VC_CONTAINERS_LIST_T *params)
|
|
{
|
|
uint32_t packetization_mode;
|
|
|
|
if (rtp_get_parameter_u32(params, "packetization-mode", &packetization_mode))
|
|
{
|
|
/* Only modes 0 and 1 are supported, no interleaving */
|
|
if (packetization_mode > 1)
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: Unsupported packetization mode: %u", packetization_mode);
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* Initialise payload bit stream for a new RTP packet.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param t_module The track module with the new RTP packet.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_new_rtp_packet(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_MODULE_T *t_module)
|
|
{
|
|
VC_CONTAINER_BITS_T *payload = &t_module->payload;
|
|
H264_PAYLOAD_T *extra = (H264_PAYLOAD_T *)t_module->extra;
|
|
uint8_t unit_header;
|
|
uint8_t fragment_header;
|
|
|
|
/* Read the NAL unit type and process as necessary */
|
|
unit_header = BITS_READ_U8(p_ctx, payload, 8, "nal_unit_header");
|
|
|
|
/* When the top bit is set, the NAL unit is invalid */
|
|
if (unit_header & NAL_UNIT_FZERO_MASK)
|
|
{
|
|
LOG_DEBUG(p_ctx, "H.264: Invalid NAL unit (top bit of header set)");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
/* In most cases, a new packet means a new NAL unit, which will need a start code and the header */
|
|
extra->header_bytes_to_write = 5;
|
|
extra->nal_header = unit_header;
|
|
extra->nal_unit_size = BITS_BYTES_AVAILABLE(p_ctx, payload);
|
|
|
|
switch (unit_header & NAL_UNIT_TYPE_MASK)
|
|
{
|
|
case NAL_UNIT_STAP_A:
|
|
/* Single Time Aggregation Packet A */
|
|
CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT);
|
|
/* Trigger reading NAL unit length and header */
|
|
extra->nal_unit_size = 0;
|
|
break;
|
|
|
|
case NAL_UNIT_FU_A:
|
|
/* Fragementation Unit A */
|
|
fragment_header = BITS_READ_U8(p_ctx, payload, 8, "fragment_header");
|
|
extra->nal_unit_size--;
|
|
|
|
if (BIT_IS_CLEAR(fragment_header, FRAGMENT_UNIT_HEADER_START) ||
|
|
BIT_IS_SET(extra->flags, H264F_INSIDE_FRAGMENT))
|
|
{
|
|
/* This is a continuation packet, prevent start code and header from being output */
|
|
extra->header_bytes_to_write = 0;
|
|
|
|
/* If this is the end of a fragment, the next FU will be a new one */
|
|
if (BIT_IS_SET(fragment_header, FRAGMENT_UNIT_HEADER_END))
|
|
CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT);
|
|
} else {
|
|
/* Start of a new fragment. */
|
|
SET_BIT(extra->flags, H264F_INSIDE_FRAGMENT);
|
|
|
|
/* Merge type from fragment header and the rest from NAL unit header to form real NAL unit header */
|
|
fragment_header &= NAL_UNIT_TYPE_MASK;
|
|
fragment_header |= (unit_header & ~NAL_UNIT_TYPE_MASK);
|
|
extra->nal_header = fragment_header;
|
|
}
|
|
break;
|
|
|
|
case NAL_UNIT_STAP_B:
|
|
case NAL_UNIT_MTAP16:
|
|
case NAL_UNIT_MTAP24:
|
|
case NAL_UNIT_FU_B:
|
|
LOG_ERROR(p_ctx, "H.264: Unsupported RTP NAL unit type: %u", unit_header & NAL_UNIT_TYPE_MASK);
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
|
|
default:
|
|
/* Single NAL unit case */
|
|
CLEAR_BIT(extra->flags, H264F_INSIDE_FRAGMENT);
|
|
}
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/**************************************************************************//**
|
|
* H.264 payload handler.
|
|
* Extracts/skips data from the payload according to the NAL unit headers.
|
|
*
|
|
* @param p_ctx The RTP container context.
|
|
* @param track The track being read.
|
|
* @param p_packet The container packet information, or NULL.
|
|
* @param flags The container read flags.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
static VC_CONTAINER_STATUS_T h264_payload_handler(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track,
|
|
VC_CONTAINER_PACKET_T *p_packet,
|
|
uint32_t flags)
|
|
{
|
|
VC_CONTAINER_TRACK_MODULE_T *t_module = track->priv->module;
|
|
VC_CONTAINER_BITS_T *payload = &t_module->payload;
|
|
H264_PAYLOAD_T *extra = (H264_PAYLOAD_T *)t_module->extra;
|
|
uint32_t packet_flags = 0;
|
|
uint8_t header_bytes_to_write;
|
|
uint32_t size, offset;
|
|
uint8_t *data_ptr;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
bool last_nal_unit_in_packet = false;
|
|
|
|
if (BIT_IS_SET(t_module->flags, TRACK_NEW_PACKET))
|
|
{
|
|
status = h264_new_rtp_packet(p_ctx, t_module);
|
|
if (status != VC_CONTAINER_SUCCESS)
|
|
return status;
|
|
}
|
|
|
|
if (BIT_IS_SET(extra->flags, H264F_NEXT_PACKET_IS_START))
|
|
{
|
|
packet_flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START;
|
|
|
|
if (!(flags & VC_CONTAINER_READ_FLAG_INFO))
|
|
CLEAR_BIT(extra->flags, H264F_NEXT_PACKET_IS_START);
|
|
}
|
|
|
|
if (!extra->nal_unit_size && BITS_BYTES_AVAILABLE(p_ctx, payload))
|
|
{
|
|
uint32_t stap_unit_header;
|
|
|
|
/* STAP-A packet: read NAL unit size and header from payload */
|
|
stap_unit_header = BITS_READ_U32(p_ctx, payload, 24, "STAP unit header");
|
|
extra->nal_unit_size = stap_unit_header >> 8;
|
|
if (extra->nal_unit_size > BITS_BYTES_AVAILABLE(p_ctx, payload))
|
|
{
|
|
LOG_ERROR(p_ctx, "H.264: STAP-A NAL unit size bigger than payload");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
extra->header_bytes_to_write = 5;
|
|
extra->nal_header = (uint8_t)stap_unit_header;
|
|
}
|
|
|
|
header_bytes_to_write = extra->header_bytes_to_write;
|
|
size = extra->nal_unit_size + header_bytes_to_write;
|
|
|
|
if (p_packet && !(flags & VC_CONTAINER_READ_FLAG_SKIP))
|
|
{
|
|
if (flags & VC_CONTAINER_READ_FLAG_INFO)
|
|
{
|
|
/* In order to set the frame end flag correctly, need to work out if this
|
|
* is the only NAL unit or last in an aggregated packet */
|
|
last_nal_unit_in_packet = (extra->nal_unit_size == BITS_BYTES_AVAILABLE(p_ctx, payload));
|
|
} else {
|
|
offset = 0;
|
|
data_ptr = p_packet->data;
|
|
|
|
if (size > p_packet->buffer_size)
|
|
{
|
|
/* Buffer not big enough */
|
|
size = p_packet->buffer_size;
|
|
}
|
|
|
|
/* Insert start code and header into the data stream */
|
|
while (offset < size && header_bytes_to_write)
|
|
{
|
|
uint8_t header_byte;
|
|
|
|
switch (header_bytes_to_write)
|
|
{
|
|
case 2: header_byte = 0x01; break;
|
|
case 1: header_byte = extra->nal_header; break;
|
|
default: header_byte = 0x00;
|
|
}
|
|
data_ptr[offset++] = header_byte;
|
|
header_bytes_to_write--;
|
|
}
|
|
extra->header_bytes_to_write = header_bytes_to_write;
|
|
|
|
if (offset < size)
|
|
{
|
|
BITS_COPY_BYTES(p_ctx, payload, size - offset, data_ptr + offset, "Packet data");
|
|
extra->nal_unit_size -= (size - offset);
|
|
}
|
|
|
|
/* If we've read the final bytes of the packet, this must be the last (or only)
|
|
* NAL unit in it */
|
|
last_nal_unit_in_packet = !BITS_BYTES_AVAILABLE(p_ctx, payload);
|
|
}
|
|
p_packet->size = size;
|
|
} else {
|
|
extra->header_bytes_to_write = 0;
|
|
BITS_SKIP_BYTES(p_ctx, payload, extra->nal_unit_size, "Packet data");
|
|
last_nal_unit_in_packet = !BITS_BYTES_AVAILABLE(p_ctx, payload);
|
|
extra->nal_unit_size = 0;
|
|
}
|
|
|
|
/* The marker bit on an RTP packet indicates the frame ends at the end of packet */
|
|
if (last_nal_unit_in_packet && BIT_IS_SET(t_module->flags, TRACK_HAS_MARKER))
|
|
{
|
|
packet_flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END;
|
|
|
|
/* If this was the last packet of a frame, the next one must be the start */
|
|
if (!(flags & VC_CONTAINER_READ_FLAG_INFO))
|
|
SET_BIT(extra->flags, H264F_NEXT_PACKET_IS_START);
|
|
}
|
|
|
|
if (p_packet)
|
|
p_packet->flags = packet_flags;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Functions exported as part of the RTP parameter handler API
|
|
*****************************************************************************/
|
|
|
|
/**************************************************************************//**
|
|
* H.264 parameter handler.
|
|
* Parses the URI parameters to set up the track for an H.264 stream.
|
|
*
|
|
* @param p_ctx The reader context.
|
|
* @param track The track to be updated.
|
|
* @param params The URI parameter list.
|
|
* @return The resulting status of the function.
|
|
*/
|
|
VC_CONTAINER_STATUS_T h264_parameter_handler(VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_T *track,
|
|
const VC_CONTAINERS_LIST_T *params)
|
|
{
|
|
H264_PAYLOAD_T *extra;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
|
|
VC_CONTAINER_PARAM_UNUSED(p_ctx);
|
|
VC_CONTAINER_PARAM_UNUSED(params);
|
|
|
|
/* See RFC3984, section 8.1, for parameter names and details. */
|
|
extra = (H264_PAYLOAD_T *)malloc(sizeof(H264_PAYLOAD_T));
|
|
if (!extra)
|
|
return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
|
|
track->priv->module->extra = extra;
|
|
memset(extra, 0, sizeof(H264_PAYLOAD_T));
|
|
|
|
/* Mandatory parameters */
|
|
status = h264_get_sprop_parameter_sets(p_ctx, track, params);
|
|
if (status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
/* Unsupported parameters */
|
|
status = h264_check_unsupported_features(p_ctx, params);
|
|
if (status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
/* Optional parameters */
|
|
status = h264_get_packetization_mode(p_ctx, params);
|
|
if (status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
track->priv->module->payload_handler = h264_payload_handler;
|
|
SET_BIT(extra->flags, H264F_NEXT_PACKET_IS_START);
|
|
|
|
track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
track->priv->module->timestamp_clock = H264_TIMESTAMP_CLOCK;
|
|
|
|
return status;
|
|
}
|