pjproject/pjmedia/src/pjmedia-codec/vid_toolbox.m

1419 lines
50 KiB
Objective-C

/*
* Copyright (C)2017 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia-codec/vid_toolbox.h>
#include <pjmedia-codec/h264_packetizer.h>
#include <pjmedia/vid_codec_util.h>
#include <pjmedia/errno.h>
#include <pj/log.h>
#include <pj/math.h>
#if defined(PJMEDIA_HAS_VID_TOOLBOX_CODEC) && \
PJMEDIA_HAS_VID_TOOLBOX_CODEC != 0 && \
defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
#import <Foundation/Foundation.h>
#import <VideoToolbox/VideoToolbox.h>
#include "TargetConditionals.h"
#define THIS_FILE "vid_toolbox.m"
#if (defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE)
#import <UIKit/UIKit.h>
#endif
#define DEFAULT_WIDTH 720
#define DEFAULT_HEIGHT 480
#define DEFAULT_FPS 15
#define DEFAULT_AVG_BITRATE 384000
#define DEFAULT_MAX_BITRATE 512000
#define MAX_RX_WIDTH 1280
#define MAX_RX_HEIGHT 800
#define SPS_PPS_BUF_SIZE 32
/* For better compatibility with other codecs (OpenH264 and x264),
* we decode the whole packets at once.
*/
#define DECODE_WHOLE PJ_TRUE
/* Maximum duration from one key frame to the next (in seconds). */
#define KEYFRAME_INTERVAL PJMEDIA_CODEC_VID_TOOLBOX_MAX_KEYFRAME_INTERVAL
/* vidtoolbox H264 default PT */
#define VT_H264_PT PJMEDIA_RTP_PT_H264_RSV1
/*
* Factory operations.
*/
static pj_status_t vtool_test_alloc(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info );
static pj_status_t vtool_default_attr(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info,
pjmedia_vid_codec_param *attr );
static pj_status_t vtool_enum_info(pjmedia_vid_codec_factory *factory,
unsigned *count,
pjmedia_vid_codec_info codecs[]);
static pj_status_t vtool_alloc_codec(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info,
pjmedia_vid_codec **p_codec);
static pj_status_t vtool_dealloc_codec(pjmedia_vid_codec_factory *factory,
pjmedia_vid_codec *codec );
/*
* Codec operations
*/
static pj_status_t vtool_codec_init(pjmedia_vid_codec *codec,
pj_pool_t *pool );
static pj_status_t vtool_codec_open(pjmedia_vid_codec *codec,
pjmedia_vid_codec_param *param );
static pj_status_t vtool_codec_close(pjmedia_vid_codec *codec);
static pj_status_t vtool_codec_modify(pjmedia_vid_codec *codec,
const pjmedia_vid_codec_param *param);
static pj_status_t vtool_codec_get_param(pjmedia_vid_codec *codec,
pjmedia_vid_codec_param *param);
static pj_status_t vtool_codec_encode_begin(pjmedia_vid_codec *codec,
const pjmedia_vid_encode_opt *opt,
const pjmedia_frame *input,
unsigned out_size,
pjmedia_frame *output,
pj_bool_t *has_more);
static pj_status_t vtool_codec_encode_more(pjmedia_vid_codec *codec,
unsigned out_size,
pjmedia_frame *output,
pj_bool_t *has_more);
static pj_status_t vtool_codec_decode(pjmedia_vid_codec *codec,
pj_size_t count,
pjmedia_frame packets[],
unsigned out_size,
pjmedia_frame *output);
/* Definition for Video Toolbox codecs operations. */
static pjmedia_vid_codec_op vtool_codec_op =
{
&vtool_codec_init,
&vtool_codec_open,
&vtool_codec_close,
&vtool_codec_modify,
&vtool_codec_get_param,
&vtool_codec_encode_begin,
&vtool_codec_encode_more,
&vtool_codec_decode,
NULL
};
/* Definition for Video Toolbox codecs factory operations. */
static pjmedia_vid_codec_factory_op vtool_factory_op =
{
&vtool_test_alloc,
&vtool_default_attr,
&vtool_enum_info,
&vtool_alloc_codec,
&vtool_dealloc_codec
};
static struct vtool_factory
{
pjmedia_vid_codec_factory base;
pjmedia_vid_codec_mgr *mgr;
pj_pool_factory *pf;
pj_pool_t *pool;
} vtool_factory;
typedef struct vtool_codec_data
{
pjmedia_vid_codec *codec;
pj_pool_t *pool;
pjmedia_vid_codec_param *prm;
pj_bool_t whole;
pjmedia_h264_packetizer *pktz;
/* Encoder */
VTCompressionSessionRef enc;
void *enc_buf;
unsigned enc_buf_size;
unsigned enc_input_size;
unsigned enc_wxh;
unsigned enc_fps;
unsigned enc_frm_cnt;
unsigned enc_frame_size;
unsigned enc_processed;
pj_bool_t enc_is_keyframe;
/* Decoder */
VTDecompressionSessionRef dec;
pj_uint8_t *dec_buf;
unsigned dec_buf_size;
CMFormatDescriptionRef dec_format;
OSStatus dec_status;
unsigned dec_sps_size;
unsigned dec_pps_size;
unsigned char dec_sps[SPS_PPS_BUF_SIZE];
unsigned char dec_pps[SPS_PPS_BUF_SIZE];
pjmedia_frame *dec_frame;
pj_bool_t dec_fmt_change;
} vtool_codec_data;
/* Prototypes */
static OSStatus create_decoder(struct vtool_codec_data *vtool_data);
#if TARGET_OS_IPHONE
static void dispatch_sync_on_main_queue(void (^block)(void))
{
if ([NSThread isMainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
#endif
PJ_DEF(pj_status_t) pjmedia_codec_vid_toolbox_init(pjmedia_vid_codec_mgr *mgr,
pj_pool_factory *pf)
{
const pj_str_t h264_name = { (char*)"H264", 4};
pj_status_t status;
if (vtool_factory.pool != NULL) {
/* Already initialized. */
return PJ_SUCCESS;
}
if (!mgr) mgr = pjmedia_vid_codec_mgr_instance();
PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
/* Create Video Toolbox codec factory. */
vtool_factory.base.op = &vtool_factory_op;
vtool_factory.base.factory_data = NULL;
vtool_factory.mgr = mgr;
vtool_factory.pf = pf;
vtool_factory.pool = pj_pool_create(pf, "vtoolfactory", 256, 256, NULL);
if (!vtool_factory.pool)
return PJ_ENOMEM;
/* Registering format match for SDP negotiation */
status = pjmedia_sdp_neg_register_fmt_match_cb(
&h264_name,
&pjmedia_vid_codec_h264_match_sdp);
if (status != PJ_SUCCESS)
goto on_error;
/* Register codec factory to codec manager. */
status = pjmedia_vid_codec_mgr_register_factory(mgr,
&vtool_factory.base);
if (status != PJ_SUCCESS)
goto on_error;
PJ_LOG(4,(THIS_FILE, "Video Toolbox codec initialized"));
/* Done. */
return PJ_SUCCESS;
on_error:
pj_pool_release(vtool_factory.pool);
vtool_factory.pool = NULL;
return status;
}
/*
* Unregister Video Toolbox codecs factory from pjmedia endpoint.
*/
PJ_DEF(pj_status_t) pjmedia_codec_vid_toolbox_deinit(void)
{
pj_status_t status = PJ_SUCCESS;
if (vtool_factory.pool == NULL) {
/* Already deinitialized */
return PJ_SUCCESS;
}
/* Unregister Video Toolbox codecs factory. */
status = pjmedia_vid_codec_mgr_unregister_factory(vtool_factory.mgr,
&vtool_factory.base);
/* Destroy pool. */
pj_pool_release(vtool_factory.pool);
vtool_factory.pool = NULL;
return status;
}
static pj_status_t vtool_test_alloc(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info )
{
PJ_ASSERT_RETURN(factory == &vtool_factory.base, PJ_EINVAL);
if (info->fmt_id == PJMEDIA_FORMAT_H264 &&
info->pt == VT_H264_PT)
{
return PJ_SUCCESS;
}
return PJMEDIA_CODEC_EUNSUP;
}
static pj_status_t vtool_default_attr(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info,
pjmedia_vid_codec_param *attr )
{
PJ_ASSERT_RETURN(factory == &vtool_factory.base, PJ_EINVAL);
PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);
pj_bzero(attr, sizeof(pjmedia_vid_codec_param));
attr->dir = PJMEDIA_DIR_ENCODING_DECODING;
attr->packing = PJMEDIA_VID_PACKING_PACKETS;
/* Encoded format */
pjmedia_format_init_video(&attr->enc_fmt, PJMEDIA_FORMAT_H264,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
/* Decoded format */
pjmedia_format_init_video(&attr->dec_fmt, PJMEDIA_FORMAT_I420,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
/* Decoding fmtp */
attr->dec_fmtp.cnt = 2;
attr->dec_fmtp.param[0].name = pj_str((char*)"profile-level-id");
attr->dec_fmtp.param[0].val = pj_str((char*)"42e01e");
attr->dec_fmtp.param[1].name = pj_str((char*)" packetization-mode");
attr->dec_fmtp.param[1].val = pj_str((char*)"1");
/* Bitrate */
attr->enc_fmt.det.vid.avg_bps = DEFAULT_AVG_BITRATE;
attr->enc_fmt.det.vid.max_bps = DEFAULT_MAX_BITRATE;
/* Encoding MTU */
attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
return PJ_SUCCESS;
}
static pj_status_t vtool_enum_info(pjmedia_vid_codec_factory *factory,
unsigned *count,
pjmedia_vid_codec_info info[])
{
PJ_ASSERT_RETURN(info && *count > 0, PJ_EINVAL);
PJ_ASSERT_RETURN(factory == &vtool_factory.base, PJ_EINVAL);
*count = 1;
info->fmt_id = PJMEDIA_FORMAT_H264;
info->pt = VT_H264_PT;
info->encoding_name = pj_str((char*)"H264");
info->encoding_desc = pj_str((char*)"Video Toolbox codec");
info->clock_rate = 90000;
info->dir = PJMEDIA_DIR_ENCODING_DECODING;
info->dec_fmt_id_cnt = 1;
info->dec_fmt_id[0] = PJMEDIA_FORMAT_I420;
info->packings = PJMEDIA_VID_PACKING_PACKETS |
PJMEDIA_VID_PACKING_WHOLE;
info->fps_cnt = 3;
info->fps[0].num = 15;
info->fps[0].denum = 1;
info->fps[1].num = 25;
info->fps[1].denum = 1;
info->fps[2].num = 30;
info->fps[2].denum = 1;
return PJ_SUCCESS;
}
static pj_status_t vtool_alloc_codec(pjmedia_vid_codec_factory *factory,
const pjmedia_vid_codec_info *info,
pjmedia_vid_codec **p_codec)
{
pj_pool_t *pool;
pjmedia_vid_codec *codec;
vtool_codec_data *vtool_data;
PJ_ASSERT_RETURN(factory == &vtool_factory.base && info && p_codec,
PJ_EINVAL);
*p_codec = NULL;
pool = pj_pool_create(vtool_factory.pf, "vtool%p", 16000, 4000, NULL);
if (!pool)
return PJ_ENOMEM;
/* codec instance */
codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec);
codec->factory = factory;
codec->op = &vtool_codec_op;
/* codec data */
vtool_data = PJ_POOL_ZALLOC_T(pool, vtool_codec_data);
vtool_data->pool = pool;
vtool_data->codec = codec;
codec->codec_data = vtool_data;
*p_codec = codec;
return PJ_SUCCESS;
}
static pj_status_t vtool_dealloc_codec(pjmedia_vid_codec_factory *factory,
pjmedia_vid_codec *codec )
{
vtool_codec_data *vtool_data;
PJ_ASSERT_RETURN(codec, PJ_EINVAL);
PJ_UNUSED_ARG(factory);
vtool_data = (vtool_codec_data*) codec->codec_data;
pj_pool_release(vtool_data->pool);
return PJ_SUCCESS;
}
static pj_status_t vtool_codec_init(pjmedia_vid_codec *codec,
pj_pool_t *pool )
{
PJ_ASSERT_RETURN(codec && pool, PJ_EINVAL);
PJ_UNUSED_ARG(codec);
PJ_UNUSED_ARG(pool);
return PJ_SUCCESS;
}
static void encode_cb(void *outputCallbackRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer)
{
struct vtool_codec_data *vtool_data;
const pj_uint8_t start_code[] = { 0, 0, 0, 1 };
const int code_size = PJ_ARRAY_SIZE(start_code);
const int avcc_size = sizeof(uint32_t);
CFArrayRef array;
CFDictionaryRef dict = NULL;
CMBlockBufferRef block_buf;
size_t offset = 0, length = 0;
size_t buf_pos;
char *data, *buf;
/* This callback can be called from another, unregistered thread.
* So do not call pjlib functions here.
*/
if (status != noErr || !CMSampleBufferDataIsReady(sampleBuffer)) return;
vtool_data = (struct vtool_codec_data *)outputCallbackRefCon;
vtool_data->enc_is_keyframe = PJ_FALSE;
buf = vtool_data->enc_buf;
/* Check if the encoded frame is keyframe */
array = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
if (array) dict = (CFDictionaryRef)CFArrayGetValueAtIndex(array, 0);
if (dict && !CFDictionaryContainsKey(dict,kCMSampleAttachmentKey_NotSync))
vtool_data->enc_is_keyframe = PJ_TRUE;
if (vtool_data->enc_is_keyframe) {
CMFormatDescriptionRef format;
size_t enc_sps_size, enc_sps_cnt;
size_t enc_pps_size, enc_pps_cnt;
const uint8_t *enc_sps, *enc_pps;
OSStatus status;
format = CMSampleBufferGetFormatDescription(sampleBuffer);
/* Get SPS */
status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
format, 0, &enc_sps, &enc_sps_size, &enc_sps_cnt, 0 );
if (status != noErr) return;
/* Get PPS */
status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
format, 1, &enc_pps, &enc_pps_size, &enc_pps_cnt, 0 );
if (status != noErr) return;
/* Append SPS and PPS to output frame */
pj_assert (enc_sps_size + enc_pps_size + 2 * code_size <=
vtool_data->enc_buf_size);
pj_memcpy(buf + offset, start_code, code_size);
offset += code_size;
pj_memcpy(buf + offset, enc_sps, enc_sps_size);
offset += enc_sps_size;
pj_memcpy(buf + offset, start_code, code_size);
offset += code_size;
pj_memcpy(buf + offset, enc_pps, enc_pps_size);
offset += enc_pps_size;
}
pj_assert(CMSampleBufferGetNumSamples(sampleBuffer) == 1);
/* Get data pointer of the encoded frame */
block_buf = CMSampleBufferGetDataBuffer(sampleBuffer);
status = CMBlockBufferGetDataPointer(block_buf, 0, &length, NULL, &data);
if (status != noErr || (offset + length) > vtool_data->enc_buf_size)
return;
pj_assert(CMBlockBufferIsRangeContiguous(block_buf, 0, length));
pj_assert(length == CMBlockBufferGetDataLength(block_buf));
buf_pos = 0;
while (buf_pos < length - avcc_size) {
uint32_t data_length;
/* Get data length and copy the data to the output buffer */
pj_memcpy(&data_length, data + buf_pos, avcc_size);
data_length = pj_ntohl(data_length);
pj_assert(buf_pos + data_length + avcc_size <= length);
pj_memcpy(buf + offset, data + buf_pos, data_length + avcc_size);
/* Replace data length with NAL start code */
pj_memcpy(buf + offset, start_code, code_size);
buf_pos += avcc_size + data_length;
offset += avcc_size + data_length;
}
vtool_data->enc_frame_size = offset;
}
static OSStatus create_encoder(vtool_codec_data *vtool_data)
{
pjmedia_vid_codec_param *param = vtool_data->prm;
CFDictionaryRef supported_prop;
OSStatus ret;
/* Destroy if initialized before */
if (vtool_data->enc) {
VTCompressionSessionInvalidate(vtool_data->enc);
CFRelease(vtool_data->enc);
vtool_data->enc = NULL;
}
/* Create encoder session */
ret = VTCompressionSessionCreate(NULL, (int)param->enc_fmt.det.vid.size.w,
(int)param->enc_fmt.det.vid.size.h,
kCMVideoCodecType_H264, NULL, NULL,
NULL, encode_cb, vtool_data,
&vtool_data->enc);
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "VTCompressionCreate failed, ret=%d", ret));
return ret;
}
#define SET_PROPERTY(sess, prop, val) \
{ \
ret = VTSessionSetProperty(sess, prop, val); \
if (ret != noErr) \
PJ_LOG(5,(THIS_FILE, "Failed to set session property %s", #prop)); \
}
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_ProfileLevel,
kVTProfileLevel_H264_Baseline_AutoLevel);
SET_PROPERTY(vtool_data->enc, kVTCompressionPropertyKey_RealTime,
kCFBooleanTrue);
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_AllowFrameReordering,
kCFBooleanFalse);
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_AverageBitRate,
(__bridge CFTypeRef)@(param->enc_fmt.det.vid.avg_bps));
vtool_data->enc_fps = param->enc_fmt.det.vid.fps.num /
param->enc_fmt.det.vid.fps.denum;
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_ExpectedFrameRate,
(__bridge CFTypeRef)@(vtool_data->enc_fps));
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_DataRateLimits,
((__bridge CFArrayRef) // [Bytes, second]
@[@(param->enc_fmt.det.vid.max_bps >> 3), @(1)]));
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_MaxKeyFrameInterval,
(__bridge CFTypeRef)@(KEYFRAME_INTERVAL *
param->enc_fmt.det.vid.fps.num /
param->enc_fmt.det.vid.fps.denum));
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration,
(__bridge CFTypeRef)@(KEYFRAME_INTERVAL));
ret = VTSessionCopySupportedPropertyDictionary(vtool_data->enc,
&supported_prop);
if (ret == noErr &&
CFDictionaryContainsKey(supported_prop,
kVTCompressionPropertyKey_MaxH264SliceBytes))
{
/* kVTCompressionPropertyKey_MaxH264SliceBytes is not yet supported
* by Apple. We leave it here for possible future enhancements.
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_MaxH264SliceBytes,
// param->enc_mtu - NAL_HEADER_ADD_0X30BYTES
(__bridge CFTypeRef)@(param->enc_mtu - 50));
*/
}
VTCompressionSessionPrepareToEncodeFrames(vtool_data->enc);
PJ_LOG(4, (THIS_FILE, "Video Toolbox encoder bitrate initialized to "
"%d avg bps and %d max bps",
param->enc_fmt.det.vid.avg_bps,
param->enc_fmt.det.vid.max_bps));
return ret;
}
static pj_status_t vtool_codec_open(pjmedia_vid_codec *codec,
pjmedia_vid_codec_param *codec_param )
{
vtool_codec_data *vtool_data;
pjmedia_vid_codec_param *param;
pjmedia_h264_packetizer_cfg pktz_cfg;
pjmedia_vid_codec_h264_fmtp h264_fmtp;
pj_status_t status;
OSStatus ret;
PJ_ASSERT_RETURN(codec && codec_param, PJ_EINVAL);
PJ_LOG(5,(THIS_FILE, "Opening codec.."));
vtool_data = (vtool_codec_data*) codec->codec_data;
vtool_data->prm = pjmedia_vid_codec_param_clone( vtool_data->pool,
codec_param);
param = vtool_data->prm;
/* Parse remote fmtp */
pj_bzero(&h264_fmtp, sizeof(h264_fmtp));
status = pjmedia_vid_codec_h264_parse_fmtp(&param->enc_fmtp, &h264_fmtp);
if (status != PJ_SUCCESS)
return status;
/* Apply SDP fmtp to format in codec param */
if (!param->ignore_fmtp) {
status = pjmedia_vid_codec_h264_apply_fmtp(param);
if (status != PJ_SUCCESS)
return status;
}
pj_bzero(&pktz_cfg, sizeof(pktz_cfg));
pktz_cfg.mtu = param->enc_mtu;
pktz_cfg.unpack_nal_start = 4;
/* Packetization mode */
if (h264_fmtp.packetization_mode == 0)
pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
else if (h264_fmtp.packetization_mode == 1)
pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
else
return PJ_ENOTSUP;
/* Video Toolbox encoder doesn't support setting maximum slice size,
* so we cannot use single NAL mode since the NAL size likely
* exceeds the MTU.
*/
pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
status = pjmedia_h264_packetizer_create(vtool_data->pool, &pktz_cfg,
&vtool_data->pktz);
if (status != PJ_SUCCESS)
return status;
vtool_data->whole = (param->packing == PJMEDIA_VID_PACKING_WHOLE);
if (1) {
/* Init format info and apply-param of encoder */
const pjmedia_video_format_info *enc_vfi;
pjmedia_video_apply_fmt_param enc_vafp;
enc_vfi = pjmedia_get_video_format_info(NULL,codec_param->dec_fmt.id);
if (!enc_vfi)
return PJ_EINVAL;
pj_bzero(&enc_vafp, sizeof(enc_vafp));
enc_vafp.size = codec_param->enc_fmt.det.vid.size;
enc_vafp.buffer = NULL;
status = (*enc_vfi->apply_fmt)(enc_vfi, &enc_vafp);
if (status != PJ_SUCCESS)
return status;
vtool_data->enc_wxh = codec_param->enc_fmt.det.vid.size.w *
codec_param->enc_fmt.det.vid.size.h;
vtool_data->enc_input_size = enc_vafp.framebytes;
if (!vtool_data->whole) {
vtool_data->enc_buf_size = (unsigned)enc_vafp.framebytes;
vtool_data->enc_buf = pj_pool_alloc(vtool_data->pool,
vtool_data->enc_buf_size);
}
}
/* Create encoder */
ret = create_encoder(vtool_data);
if (ret != noErr)
return PJMEDIA_CODEC_EFAILED;
/* If available, use the "sprop-parameter-sets" fmtp from remote SDP
* to create the decoder.
*/
if (h264_fmtp.sprop_param_sets_len) {
const pj_uint8_t start_code[3] = {0, 0, 1};
const int code_size = PJ_ARRAY_SIZE(start_code);
unsigned i, j;
for (i = h264_fmtp.sprop_param_sets_len-code_size; i >= code_size;
i--)
{
for (j = 0; j < code_size; j++) {
if (h264_fmtp.sprop_param_sets[i+j] != start_code[j]) {
break;
}
}
}
if (i >= code_size) {
vtool_data->dec_sps_size = i - code_size;
pj_memcpy(vtool_data->dec_sps,
&h264_fmtp.sprop_param_sets[code_size],
vtool_data->dec_sps_size);
vtool_data->dec_pps_size = h264_fmtp.sprop_param_sets_len -
code_size-i;
pj_memcpy(vtool_data->dec_pps,
&h264_fmtp.sprop_param_sets[i + code_size],
vtool_data->dec_pps_size);
create_decoder(vtool_data);
}
}
/* Create decoder buffer */
vtool_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * 3 >> 1) +
(MAX_RX_WIDTH);
vtool_data->dec_buf = (pj_uint8_t*)pj_pool_alloc(vtool_data->pool,
vtool_data->dec_buf_size);
/* Need to update param back after values are negotiated */
pj_memcpy(codec_param, param, sizeof(*codec_param));
return PJ_SUCCESS;
}
static pj_status_t vtool_codec_close(pjmedia_vid_codec *codec)
{
struct vtool_codec_data *vtool_data;
PJ_ASSERT_RETURN(codec, PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
if (vtool_data->enc) {
VTCompressionSessionInvalidate(vtool_data->enc);
CFRelease(vtool_data->enc);
vtool_data->enc = NULL;
}
if (vtool_data->dec) {
VTDecompressionSessionInvalidate(vtool_data->dec);
CFRelease(vtool_data->dec);
vtool_data->dec = NULL;
}
if (vtool_data->dec_format)
CFRelease(vtool_data->dec_format);
return PJ_SUCCESS;
}
static pj_status_t vtool_codec_modify(pjmedia_vid_codec *codec,
const pjmedia_vid_codec_param *param)
{
struct vtool_codec_data *vtool_data;
OSStatus ret;
PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_AverageBitRate,
(__bridge CFTypeRef)@(param->enc_fmt.det.vid.avg_bps));
if (ret != noErr)
return PJMEDIA_CODEC_EUNSUP;
vtool_data->prm->enc_fmt.det.vid.avg_bps = param->enc_fmt.det.vid.avg_bps;
SET_PROPERTY(vtool_data->enc,
kVTCompressionPropertyKey_DataRateLimits,
((__bridge CFArrayRef) // [Bytes, second]
@[@(param->enc_fmt.det.vid.max_bps >> 3), @(1)]));
if (ret == noErr) {
vtool_data->prm->enc_fmt.det.vid.max_bps =
param->enc_fmt.det.vid.max_bps;
PJ_LOG(4, (THIS_FILE, "Video Toolbox encoder bitrate is modified to "
"%d avg bps and %d max bps",
param->enc_fmt.det.vid.avg_bps,
param->enc_fmt.det.vid.max_bps));
}
return PJ_SUCCESS;
}
static pj_status_t vtool_codec_get_param(pjmedia_vid_codec *codec,
pjmedia_vid_codec_param *param)
{
struct vtool_codec_data *vtool_data;
PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
pj_memcpy(param, vtool_data->prm, sizeof(*param));
return PJ_SUCCESS;
}
static pj_status_t vtool_codec_encode_begin(pjmedia_vid_codec *codec,
const pjmedia_vid_encode_opt *opt,
const pjmedia_frame *input,
unsigned out_size,
pjmedia_frame *output,
pj_bool_t *has_more)
{
struct vtool_codec_data *vtool_data;
CMTime ts, dur;
CVImageBufferRef image_buf;
void *base_addr[3];
size_t plane_w[3], plane_h[3], plane_bpr[3];
NSDictionary *frm_prop = NULL;
OSStatus ret;
PJ_ASSERT_RETURN(codec && input && out_size && output && has_more,
PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
base_addr[0] = input->buf;
base_addr[1] = input->buf + vtool_data->enc_wxh;
base_addr[2] = base_addr[1] + (vtool_data->enc_wxh >> 2);
plane_w[0] = vtool_data->prm->enc_fmt.det.vid.size.w;
plane_h[0] = vtool_data->prm->enc_fmt.det.vid.size.h;
plane_w[1] = plane_w[2] = vtool_data->prm->enc_fmt.det.vid.size.w >> 1;
plane_h[1] = plane_h[2] = vtool_data->prm->enc_fmt.det.vid.size.h >> 1;
plane_bpr[0] = vtool_data->prm->enc_fmt.det.vid.size.w;
plane_bpr[1] = plane_bpr[2] = vtool_data->prm->enc_fmt.det.vid.size.w >> 1;
#if TARGET_OS_IPHONE
ret = CVPixelBufferCreate(NULL,
vtool_data->prm->enc_fmt.det.vid.size.w,
vtool_data->prm->enc_fmt.det.vid.size.h,
kCVPixelFormatType_420YpCbCr8Planar, /* I420 */
NULL, &image_buf);
if (ret == noErr) {
size_t i, count;
CVPixelBufferLockBaseAddress(image_buf, 0);
count = CVPixelBufferGetPlaneCount(image_buf);
for (i = 0; i < count; i++) {
char *ptr = (char*)CVPixelBufferGetBaseAddressOfPlane(image_buf, i);
char *src = (char*)base_addr[i];
size_t bpr = CVPixelBufferGetBytesPerRowOfPlane(image_buf, i);
int j;
pj_assert(bpr >= plane_bpr[i]);
for (j = 0; j < plane_h[i]; ++j) {
pj_memcpy(ptr, src, plane_bpr[i]);
src += plane_bpr[i];
ptr += bpr;
}
}
CVPixelBufferUnlockBaseAddress(image_buf, 0);
}
#else
ret = CVPixelBufferCreateWithPlanarBytes(NULL,
vtool_data->prm->enc_fmt.det.vid.size.w,
vtool_data->prm->enc_fmt.det.vid.size.h,
kCVPixelFormatType_420YpCbCr8Planar, /* I420 */
NULL, vtool_data->enc_input_size,
3, /* number of planes of I420 */
base_addr,
(size_t *)plane_w, (size_t *)plane_h, (size_t *)plane_bpr,
NULL, NULL, NULL, &image_buf);
#endif
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "Failed to create pixel buffer"));
return PJMEDIA_CODEC_EFAILED;
}
ts = CMTimeMake(++vtool_data->enc_frm_cnt, vtool_data->enc_fps);
dur = CMTimeMake(1, vtool_data->enc_fps);
vtool_data->enc_frame_size = vtool_data->enc_processed = 0;
if (vtool_data->whole) {
vtool_data->enc_buf = output->buf;
vtool_data->enc_buf_size = out_size;
}
if (opt && opt->force_keyframe) {
frm_prop = @{ (__bridge NSString *)
kVTEncodeFrameOptionKey_ForceKeyFrame: @YES };
}
ret = VTCompressionSessionEncodeFrame(vtool_data->enc, image_buf,
ts, dur,
(__bridge CFDictionaryRef)frm_prop,
NULL, NULL);
if (ret == kVTInvalidSessionErr) {
#if TARGET_OS_IPHONE
/* Just return if app is not active, i.e. in the bg. */
__block UIApplicationState state;
dispatch_sync_on_main_queue(^{
state = [UIApplication sharedApplication].applicationState;
});
if (state != UIApplicationStateActive) {
*has_more = PJ_FALSE;
output->size = 0;
output->type = PJMEDIA_FRAME_TYPE_NONE;
CVPixelBufferRelease(image_buf);
return PJ_SUCCESS;
}
#endif
/* Reset compression session */
ret = create_encoder(vtool_data);
PJ_LOG(3,(THIS_FILE, "Encoder needs to be reset [1]: %s (%d)",
(ret == noErr? "success": "fail"), ret));
if (ret == noErr) {
/* Retry encoding the frame after successful encoder reset. */
ret = VTCompressionSessionEncodeFrame(vtool_data->enc, image_buf,
ts, dur,
(__bridge CFDictionaryRef)
frm_prop,
NULL, NULL);
}
}
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "Failed to encode frame %d", ret));
CVPixelBufferRelease(image_buf);
return PJMEDIA_CODEC_EFAILED;
}
/* EncodeFrame is async, so tell it to finish the encoding. */
ts.flags = kCMTimeFlags_Indefinite;
ret = VTCompressionSessionCompleteFrames(vtool_data->enc, ts);
if (ret == kVTInvalidSessionErr) {
/* Reset compression session */
ret = create_encoder(vtool_data);
PJ_LOG(3,(THIS_FILE, "Encoder needs to be reset [2]: %s (%d)",
(ret == noErr? "success": "fail"), ret));
if (ret == PJ_SUCCESS) {
/* Retry finishing the encoding after successful encoder reset. */
ret = VTCompressionSessionCompleteFrames(vtool_data->enc, ts);
}
}
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "Failed to complete encoding %d", ret));
CVPixelBufferRelease(image_buf);
return PJMEDIA_CODEC_EFAILED;
}
CVPixelBufferRelease(image_buf);
if (vtool_data->whole) {
*has_more = PJ_FALSE;
output->size = vtool_data->enc_frame_size;
return PJ_SUCCESS;
}
return vtool_codec_encode_more(codec, out_size, output, has_more);
}
static pj_status_t vtool_codec_encode_more(pjmedia_vid_codec *codec,
unsigned out_size,
pjmedia_frame *output,
pj_bool_t *has_more)
{
struct vtool_codec_data *vtool_data;
const pj_uint8_t *payload;
pj_size_t payload_len;
pj_status_t status;
PJ_ASSERT_RETURN(codec && out_size && output && has_more,
PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
if (vtool_data->enc_processed >= vtool_data->enc_frame_size) {
/* No more frame */
*has_more = PJ_FALSE;
output->size = 0;
output->type = PJMEDIA_FRAME_TYPE_NONE;
return PJ_SUCCESS;
}
/* We have outstanding frame in packetizer */
status = pjmedia_h264_packetize(vtool_data->pktz,
(pj_uint8_t*)vtool_data->enc_buf,
vtool_data->enc_frame_size,
&vtool_data->enc_processed,
&payload, &payload_len);
if (status != PJ_SUCCESS) {
/* Reset */
vtool_data->enc_frame_size = vtool_data->enc_processed = 0;
*has_more = (vtool_data->enc_processed < vtool_data->enc_frame_size);
PJ_PERROR(4,(THIS_FILE, status, "pjmedia_h264_packetize() error"));
return status;
}
PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT);
output->type = PJMEDIA_FRAME_TYPE_VIDEO;
pj_memcpy(output->buf, payload, payload_len);
output->size = payload_len;
if (vtool_data->enc_is_keyframe)
output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
*has_more = (vtool_data->enc_processed < vtool_data->enc_frame_size);
return PJ_SUCCESS;
}
/* Copy I420 frame from source to destination and clip if necessary */
static int process_i420(CVImageBufferRef src_buf, pj_uint8_t *dst)
{
pj_uint8_t *pdst = dst;
pj_size_t i, count;
count = CVPixelBufferGetPlaneCount(src_buf);
for (i = 0; i < count; i++) {
pj_uint8_t *psrc;
pj_size_t src_w, dst_w, h;
psrc = CVPixelBufferGetBaseAddressOfPlane(src_buf, i);
src_w = CVPixelBufferGetBytesPerRowOfPlane(src_buf, i);
dst_w = CVPixelBufferGetWidthOfPlane(src_buf, i);
h = CVPixelBufferGetHeightOfPlane(src_buf, i);
/* Check if clipping is required */
if (src_w == dst_w) {
pj_size_t plane_size = dst_w * h;
pj_memcpy(pdst, psrc, plane_size);
pdst += plane_size;
} else {
pj_size_t j = 0;
for (; j < h; ++j) {
pj_memcpy(pdst, psrc, dst_w);
pdst += dst_w;
psrc += src_w;
}
}
}
return (pdst - dst);
}
static void decode_cb(void *decompressionOutputRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration)
{
struct vtool_codec_data *vtool_data;
pj_size_t width, height, len = 0;
/* This callback can be called from another, unregistered thread.
* So do not call pjlib functions here.
*/
vtool_data = (struct vtool_codec_data *)decompressionOutputRefCon;
vtool_data->dec_status = status;
if (vtool_data->dec_status != noErr)
return;
CVPixelBufferLockBaseAddress(imageBuffer,0);
width = CVPixelBufferGetWidth(imageBuffer);
height = CVPixelBufferGetHeight(imageBuffer);
/* Detect format change */
if (width != vtool_data->prm->dec_fmt.det.vid.size.w ||
height != vtool_data->prm->dec_fmt.det.vid.size.h)
{
vtool_data->dec_fmt_change = PJ_TRUE;
vtool_data->prm->dec_fmt.det.vid.size.w = width;
vtool_data->prm->dec_fmt.det.vid.size.h = height;
} else {
vtool_data->dec_fmt_change = PJ_FALSE;
}
if (vtool_data->dec_frame->size >= width * height * 3 / 2) {
len = process_i420(imageBuffer,
(pj_uint8_t *)vtool_data->dec_frame->buf);
} else {
vtool_data->dec_status = (OSStatus)PJMEDIA_CODEC_EFRMTOOSHORT;
}
vtool_data->dec_frame->size = len;
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
}
static OSStatus create_decoder(struct vtool_codec_data *vtool_data)
{
uint8_t *param_ptrs[2] = {vtool_data->dec_sps,
vtool_data->dec_pps};
const size_t param_sizes[2] = {vtool_data->dec_sps_size,
vtool_data->dec_pps_size};
const int code_size = 4; // PJ_ARRAY_SIZE(start_code);
CMFormatDescriptionRef dec_format;
VTDecompressionOutputCallbackRecord cbr;
NSDictionary *dst_attr;
OSStatus ret;
/* Create video format description based on H264 SPS and PPS
* parameters.
*/
ret = CMVideoFormatDescriptionCreateFromH264ParameterSets(
kCFAllocatorDefault, 2,
(const uint8_t * const *)param_ptrs,
param_sizes, code_size, &dec_format);
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "Failed to create video format "
"description %d", ret));
return ret;
}
if (!vtool_data->dec || !vtool_data->dec_format ||
!CMFormatDescriptionEqual(dec_format, vtool_data->dec_format))
{
if (vtool_data->dec_format)
CFRelease(vtool_data->dec_format);
vtool_data->dec_format = dec_format;
} else {
CFRelease(dec_format);
return noErr;
}
cbr.decompressionOutputCallback = decode_cb;
cbr.decompressionOutputRefCon = vtool_data;
if (vtool_data->dec) {
VTDecompressionSessionInvalidate(vtool_data->dec);
CFRelease(vtool_data->dec);
vtool_data->dec = NULL;
}
dst_attr = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt: /* I420 */
kCVPixelFormatType_420YpCbCr8Planar],
kCVPixelBufferPixelFormatTypeKey,
nil];
ret = VTDecompressionSessionCreate(NULL, vtool_data->dec_format, NULL,
(__bridge CFDictionaryRef)dst_attr,
&cbr, &vtool_data->dec);
if (ret != noErr) {
PJ_LOG(3,(THIS_FILE, "Failed to create decompression session %d",
ret));
}
SET_PROPERTY(vtool_data->dec, kVTCompressionPropertyKey_RealTime,
kCFBooleanTrue);
#if !TARGET_OS_IPHONE
SET_PROPERTY(vtool_data->dec,
kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
kCFBooleanTrue);
#endif
return ret;
}
static pj_status_t vtool_codec_decode(pjmedia_vid_codec *codec,
pj_size_t count,
pjmedia_frame packets[],
unsigned out_size,
pjmedia_frame *output)
{
struct vtool_codec_data *vtool_data;
const pj_uint8_t start_code[] = { 0, 0, 0, 1 };
const int code_size = PJ_ARRAY_SIZE(start_code);
pj_bool_t has_frame = PJ_FALSE;
unsigned buf_pos, whole_len = 0;
unsigned i;
pj_status_t status = PJ_SUCCESS;
pj_bool_t decode_whole = DECODE_WHOLE;
OSStatus ret;
PJ_ASSERT_RETURN(codec && count && packets && out_size && output,
PJ_EINVAL);
PJ_ASSERT_RETURN(output->buf, PJ_EINVAL);
vtool_data = (vtool_codec_data*) codec->codec_data;
/*
* Step 1: unpacketize the packets/frames
*/
whole_len = 0;
if (vtool_data->whole) {
for (i=0; i<count; ++i) {
if (whole_len + packets[i].size > vtool_data->dec_buf_size) {
PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow"));
status = PJMEDIA_CODEC_EFRMTOOSHORT;
break;
}
pj_memcpy( vtool_data->dec_buf + whole_len,
(pj_uint8_t*)packets[i].buf,
packets[i].size);
whole_len += packets[i].size;
}
} else {
for (i=0; i<count; ++i) {
if (whole_len + packets[i].size + code_size >
vtool_data->dec_buf_size)
{
PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]"));
status = PJMEDIA_CODEC_EFRMTOOSHORT;
break;
}
status = pjmedia_h264_unpacketize( vtool_data->pktz,
(pj_uint8_t*)packets[i].buf,
packets[i].size,
vtool_data->dec_buf,
vtool_data->dec_buf_size,
&whole_len);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE, status, "Unpacketize error"));
continue;
}
}
}
if (whole_len + code_size > vtool_data->dec_buf_size ||
whole_len <= code_size + 1)
{
PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow or unpacketize error "
"size: %d, buffer: %d", whole_len,
vtool_data->dec_buf_size));
status = PJMEDIA_CODEC_EFRMTOOSHORT;
}
if (status != PJ_SUCCESS)
goto on_return;
/* Dummy NAL sentinel */
pj_memcpy(vtool_data->dec_buf + whole_len, start_code, code_size);
/*
* Step 2: parse the individual NAL and give to decoder
*/
buf_pos = 0;
while (1) {
uint32_t frm_size, nalu_type, data_length;
unsigned char *start;
for (i = code_size - 1; buf_pos + i < whole_len; i++) {
if (vtool_data->dec_buf[buf_pos + i] == 0 &&
vtool_data->dec_buf[buf_pos + i + 1] == 0 &&
vtool_data->dec_buf[buf_pos + i + 2] == 0 &&
vtool_data->dec_buf[buf_pos + i + 3] == 1)
{
break;
}
}
frm_size = i;
start = vtool_data->dec_buf + buf_pos;
nalu_type = (start[code_size] & 0x1F);
#if TARGET_OS_IPHONE
/* On iOS, packets preceded by SEI frame (type 6), such as the ones
* sent by Mac VideoToolbox encoder will cause DecodeFrame to fail
* with -12911 (kVTVideoDecoderMalfunctionErr). The workaround
* is to decode the whole packets at once.
*/
if (nalu_type == 6)
decode_whole = PJ_TRUE;
#endif
/* AVCC format requires us to replace the start code header
* on this NAL with its frame size.
*/
data_length = pj_htonl(frm_size - code_size);
pj_memcpy(start, &data_length, sizeof (data_length));
if (nalu_type == 7) {
/* NALU type 7 is the SPS parameter NALU */
vtool_data->dec_sps_size = PJ_MIN(frm_size - code_size,
sizeof(vtool_data->dec_sps));
pj_memcpy(vtool_data->dec_sps, &start[code_size],
vtool_data->dec_sps_size);
} else if (nalu_type == 8) {
/* NALU type 8 is the PPS parameter NALU */
vtool_data->dec_pps_size = PJ_MIN(frm_size - code_size,
sizeof(vtool_data->dec_pps));
pj_memcpy(vtool_data->dec_pps, &start[code_size],
vtool_data->dec_pps_size);
ret = create_decoder(vtool_data);
} else if (vtool_data->dec &&
(!decode_whole || (buf_pos + frm_size >= whole_len)))
{
CMBlockBufferRef block_buf = NULL;
CMSampleBufferRef sample_buf = NULL;
if (decode_whole) {
/* We decode all the packets at once. */
frm_size = whole_len;
start = vtool_data->dec_buf;
}
/* Create a block buffer from the NALU */
ret = CMBlockBufferCreateWithMemoryBlock(NULL,
start, frm_size,
kCFAllocatorNull, NULL,
0, frm_size,
0, &block_buf);
if (ret == noErr) {
const size_t sample_size = frm_size;
ret = CMSampleBufferCreate(kCFAllocatorDefault,
block_buf, true, NULL, NULL,
vtool_data->dec_format,
1, 0, NULL, 1,
&sample_size, &sample_buf);
if (ret != noErr) {
PJ_LOG(4,(THIS_FILE, "Failed to create sample buffer"));
CFRelease(block_buf);
}
} else {
PJ_LOG(4,(THIS_FILE, "Failed to create block buffer"));
}
if (ret == noErr) {
vtool_data->dec_frame = output;
vtool_data->dec_frame->size = out_size;
ret = VTDecompressionSessionDecodeFrame(
vtool_data->dec, sample_buf, 0,
NULL, NULL);
if (ret == kVTInvalidSessionErr) {
#if TARGET_OS_IPHONE
/* Just return if app is not active, i.e. in the bg. */
__block UIApplicationState state;
dispatch_sync_on_main_queue(^{
state = [UIApplication sharedApplication].applicationState;
});
if (state != UIApplicationStateActive) {
output->type = PJMEDIA_FRAME_TYPE_NONE;
output->size = 0;
output->timestamp = packets[0].timestamp;
CFRelease(block_buf);
CFRelease(sample_buf);
return PJ_SUCCESS;
}
#endif
if (vtool_data->dec_format)
CFRelease(vtool_data->dec_format);
vtool_data->dec_format = NULL;
ret = create_decoder(vtool_data);
PJ_LOG(3,(THIS_FILE, "Decoder needs to be reset: %s (%d)",
(ret == noErr? "success": "fail"), ret));
if (ret == noErr) {
/* Retry decoding the frame after successful reset */
ret = VTDecompressionSessionDecodeFrame(
vtool_data->dec, sample_buf, 0,
NULL, NULL);
}
}
if ((ret != noErr) || (vtool_data->dec_status != noErr)) {
char *ret_err = (ret != noErr)?"decode err":"cb err";
OSStatus err_code = (ret != noErr)? ret:
vtool_data->dec_status;
PJ_LOG(5,(THIS_FILE, "Failed to decode frame %d of size "
"%d %s:%d", nalu_type, frm_size, ret_err,
err_code));
} else {
has_frame = PJ_TRUE;
output->type = PJMEDIA_FRAME_TYPE_VIDEO;
output->timestamp = packets[0].timestamp;
/* Broadcast format changed event */
if (vtool_data->dec_fmt_change) {
pjmedia_event event;
PJ_LOG(4,(THIS_FILE, "Frame size changed to %dx%d",
vtool_data->prm->dec_fmt.det.vid.size.w,
vtool_data->prm->dec_fmt.det.vid.size.h));
/* Broadcast format changed event */
pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED,
&output->timestamp, codec);
event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
pjmedia_format_copy(&event.data.fmt_changed.new_fmt,
&vtool_data->prm->dec_fmt);
pjmedia_event_publish(NULL, codec, &event,
PJMEDIA_EVENT_PUBLISH_DEFAULT);
}
}
CFRelease(block_buf);
CFRelease(sample_buf);
}
}
if (buf_pos + frm_size >= whole_len)
break;
buf_pos += frm_size;
}
on_return:
if (!has_frame) {
pjmedia_event event;
/* Broadcast missing keyframe event */
pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING,
&packets[0].timestamp, codec);
pjmedia_event_publish(NULL, codec, &event,
PJMEDIA_EVENT_PUBLISH_DEFAULT);
PJ_LOG(5,(THIS_FILE, "Decode couldn't produce picture, "
"input nframes=%lu, concatenated size=%d bytes",
(unsigned long)count, whole_len));
output->type = PJMEDIA_FRAME_TYPE_NONE;
output->size = 0;
output->timestamp = packets[0].timestamp;
}
return PJ_SUCCESS;
}
#endif /* PJMEDIA_HAS_VID_TOOLBOX_CODEC */