bool warned_color_range;
} VTEncContext;
+static int vtenc_populate_extradata(AVCodecContext *avctx,
+ CMVideoCodecType codec_type,
+ CFStringRef profile_level,
+ CFNumberRef gamma_level,
+ CFDictionaryRef enc_info,
+ CFDictionaryRef pixel_buffer_info);
+
/**
* NULL-safe release of *refPtr, and sets value to NULL.
*/
pthread_mutex_unlock(&vtctx->lock);
}
+static int count_nalus(size_t length_code_size,
+ CMSampleBufferRef sample_buffer,
+ int *count)
+{
+ size_t offset = 0;
+ int status;
+ int nalu_ct = 0;
+ uint8_t size_buf[4];
+ size_t src_size = CMSampleBufferGetTotalSampleSize(sample_buffer);
+ CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sample_buffer);
+
+ if (length_code_size > 4)
+ return AVERROR_INVALIDDATA;
+
+ while (offset < src_size) {
+ size_t curr_src_len;
+ size_t box_len = 0;
+ size_t i;
+
+ status = CMBlockBufferCopyDataBytes(block,
+ offset,
+ length_code_size,
+ size_buf);
+
+ for (i = 0; i < length_code_size; i++) {
+ box_len <<= 8;
+ box_len |= size_buf[i];
+ }
+
+ curr_src_len = box_len + length_code_size;
+ offset += curr_src_len;
+
+ nalu_ct++;
+ }
+
+ *count = nalu_ct;
+ return 0;
+}
+
static CMVideoCodecType get_cm_codec_type(enum AVCodecID id)
{
switch (id) {
}
}
-static void vtenc_free_block(void *opaque, uint8_t *data)
-{
- CMBlockBufferRef block = opaque;
- CFRelease(block);
-}
-
/**
* Get the parameter sets from a CMSampleBufferRef.
* @param dst If *dst isn't NULL, the parameters are copied into existing
return status;
}
- avctx->extradata = av_malloc(total_size);
+ avctx->extradata = av_mallocz(total_size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!avctx->extradata) {
return AVERROR(ENOMEM);
}
return 0;
}
-
-static av_cold int vtenc_init(AVCodecContext *avctx)
+static int vtenc_create_encoder(AVCodecContext *avctx,
+ CMVideoCodecType codec_type,
+ CFStringRef profile_level,
+ CFNumberRef gamma_level,
+ CFDictionaryRef enc_info,
+ CFDictionaryRef pixel_buffer_info,
+ VTCompressionSessionRef *session)
{
- CFMutableDictionaryRef enc_info;
- CFMutableDictionaryRef pixel_buffer_info;
- CMVideoCodecType codec_type;
- VTEncContext *vtctx = avctx->priv_data;
- CFStringRef profile_level;
- SInt32 bit_rate = avctx->bit_rate;
- CFNumberRef bit_rate_num;
- CFBooleanRef has_b_frames_cfbool;
- CFNumberRef gamma_level;
- int status;
-
- codec_type = get_cm_codec_type(avctx->codec_id);
- if (!codec_type) {
- av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id);
- return AVERROR(EINVAL);
- }
-
- vtctx->has_b_frames = avctx->max_b_frames > 0;
- if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){
- av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");
- vtctx->has_b_frames = false;
- }
-
- if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {
- av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");
- vtctx->entropy = VT_ENTROPY_NOT_SET;
- }
-
- if (!get_vt_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
-
- vtctx->session = NULL;
-
- enc_info = CFDictionaryCreateMutable(
- kCFAllocatorDefault,
- 20,
- &kCFCopyStringDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks
- );
-
- if (!enc_info) return AVERROR(ENOMEM);
-
-#if !TARGET_OS_IPHONE
- if (!vtctx->allow_sw) {
- CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
- } else {
- CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
- }
-#endif
-
- if (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) {
- status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info);
- if (status) {
- CFRelease(enc_info);
- return status;
- }
- } else {
- pixel_buffer_info = NULL;
- }
-
- status = VTCompressionSessionCreate(
- kCFAllocatorDefault,
- avctx->width,
- avctx->height,
- codec_type,
- enc_info,
- pixel_buffer_info,
- kCFAllocatorDefault,
- vtenc_output_callback,
- avctx,
- &vtctx->session
- );
-
- if (pixel_buffer_info) CFRelease(pixel_buffer_info);
- CFRelease(enc_info);
+ VTEncContext *vtctx = avctx->priv_data;
+ SInt32 bit_rate = avctx->bit_rate;
+ CFNumberRef bit_rate_num;
+
+ int status = VTCompressionSessionCreate(kCFAllocatorDefault,
+ avctx->width,
+ avctx->height,
+ codec_type,
+ enc_info,
+ pixel_buffer_info,
+ kCFAllocatorDefault,
+ vtenc_output_callback,
+ avctx,
+ session);
if (status || !vtctx->session) {
av_log(avctx, AV_LOG_ERROR, "Error: cannot create compression session: %d\n", status);
}
}
- status = get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level);
- if (!status && vtctx->transfer_function) {
+
+ if (vtctx->transfer_function) {
status = VTSessionSetProperty(vtctx->session,
kVTCompressionPropertyKey_TransferFunction,
vtctx->transfer_function);
}
}
- status = get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix);
- if (!status && vtctx->ycbcr_matrix) {
+
+ if (vtctx->ycbcr_matrix) {
status = VTSessionSetProperty(vtctx->session,
kVTCompressionPropertyKey_YCbCrMatrix,
vtctx->ycbcr_matrix);
}
}
- status = get_cv_color_primaries(avctx, &vtctx->color_primaries);
- if (!status && vtctx->color_primaries) {
+
+ if (vtctx->color_primaries) {
status = VTSessionSetProperty(vtctx->session,
kVTCompressionPropertyKey_ColorPrimaries,
vtctx->color_primaries);
}
}
- if (!status && gamma_level) {
+ if (gamma_level) {
status = VTSessionSetProperty(vtctx->session,
kCVImageBufferGammaLevelKey,
gamma_level);
return AVERROR_EXTERNAL;
}
+ return 0;
+}
+
+static av_cold int vtenc_init(AVCodecContext *avctx)
+{
+ CFMutableDictionaryRef enc_info;
+ CFMutableDictionaryRef pixel_buffer_info;
+ CMVideoCodecType codec_type;
+ VTEncContext *vtctx = avctx->priv_data;
+ CFStringRef profile_level;
+ CFBooleanRef has_b_frames_cfbool;
+ CFNumberRef gamma_level = NULL;
+ int status;
+
+ codec_type = get_cm_codec_type(avctx->codec_id);
+ if (!codec_type) {
+ av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id);
+ return AVERROR(EINVAL);
+ }
+
+ vtctx->has_b_frames = avctx->max_b_frames > 0;
+ if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){
+ av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");
+ vtctx->has_b_frames = false;
+ }
+
+ if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {
+ av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");
+ vtctx->entropy = VT_ENTROPY_NOT_SET;
+ }
+
+ if (!get_vt_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
+
+ vtctx->session = NULL;
+
+ enc_info = CFDictionaryCreateMutable(
+ kCFAllocatorDefault,
+ 20,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks
+ );
+
+ if (!enc_info) return AVERROR(ENOMEM);
+
+#if !TARGET_OS_IPHONE
+ if (!vtctx->allow_sw) {
+ CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
+ } else {
+ CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder, kCFBooleanTrue);
+ }
+#endif
+
+ if (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) {
+ status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info);
+ if (status)
+ goto init_cleanup;
+ } else {
+ pixel_buffer_info = NULL;
+ }
+
pthread_mutex_init(&vtctx->lock, NULL);
pthread_cond_init(&vtctx->cv_sample_sent, NULL);
vtctx->dts_delta = vtctx->has_b_frames ? -1 : 0;
+ get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level);
+ get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix);
+ get_cv_color_primaries(avctx, &vtctx->color_primaries);
+
+
+ if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
+ status = vtenc_populate_extradata(avctx,
+ codec_type,
+ profile_level,
+ gamma_level,
+ enc_info,
+ pixel_buffer_info);
+ if (status)
+ goto init_cleanup;
+ }
+
+ status = vtenc_create_encoder(avctx,
+ codec_type,
+ profile_level,
+ gamma_level,
+ enc_info,
+ pixel_buffer_info,
+ &vtctx->session);
+
+ if (status < 0)
+ goto init_cleanup;
+
status = VTSessionCopyProperty(vtctx->session,
kVTCompressionPropertyKey_AllowFrameReordering,
kCFAllocatorDefault,
}
avctx->has_b_frames = vtctx->has_b_frames;
- return 0;
+init_cleanup:
+ if (gamma_level)
+ CFRelease(gamma_level);
+
+ if (pixel_buffer_info)
+ CFRelease(pixel_buffer_info);
+
+ CFRelease(enc_info);
+
+ return status;
}
static void vtenc_get_frame_info(CMSampleBufferRef buffer, bool *is_key_frame)
}
}
-/**
- * Replaces length codes with H.264 Annex B start codes.
- * length_code_size must equal sizeof(start_code).
- * On failure, the contents of data may have been modified.
- *
- * @param length_code_size Byte length of each length code
- * @param data Call with NAL units prefixed with length codes.
- * On success, the length codes are replace with
- * start codes.
- * @param size Length of data, excluding any padding.
- * @return 0 on success
- * AVERROR_BUFFER_TOO_SMALL if length code size is smaller
- * than a start code or if a length_code in data specifies
- * data beyond the end of its buffer.
- */
-static int replace_length_codes(size_t length_code_size,
- uint8_t *data,
- size_t size)
-{
- size_t remaining_size = size;
-
- if (length_code_size != sizeof(start_code)) {
- av_log(NULL, AV_LOG_ERROR, "Start code size and length code size not equal.\n");
- return AVERROR_BUFFER_TOO_SMALL;
- }
-
- while (remaining_size > 0) {
- size_t box_len = 0;
- size_t i;
-
- for (i = 0; i < length_code_size; i++) {
- box_len <<= 8;
- box_len |= data[i];
- }
-
- if (remaining_size < box_len + sizeof(start_code)) {
- av_log(NULL, AV_LOG_ERROR, "Length is out of range.\n");
- AVERROR_BUFFER_TOO_SMALL;
- }
-
- memcpy(data, start_code, sizeof(start_code));
- data += box_len + sizeof(start_code);
- remaining_size -= box_len + sizeof(start_code);
- }
-
- return 0;
-}
-
/**
* Copies NAL units and replaces length codes with
* H.264 Annex B start codes. On failure, the contents of
* the end of its buffer.
*/
static int copy_replace_length_codes(
+ AVCodecContext *avctx,
size_t length_code_size,
- const uint8_t *src_data,
- size_t src_size,
+ CMSampleBufferRef sample_buffer,
uint8_t *dst_data,
size_t dst_size)
{
+ size_t src_size = CMSampleBufferGetTotalSampleSize(sample_buffer);
size_t remaining_src_size = src_size;
size_t remaining_dst_size = dst_size;
+ size_t src_offset = 0;
+ int status;
+ uint8_t size_buf[4];
+ CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sample_buffer);
if (length_code_size > 4) {
return AVERROR_INVALIDDATA;
size_t i;
uint8_t *dst_box;
- const uint8_t *src_box;
+
+ status = CMBlockBufferCopyDataBytes(block,
+ src_offset,
+ length_code_size,
+ size_buf);
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "Cannot copy length: %d\n", status);
+ return AVERROR_EXTERNAL;
+ }
for (i = 0; i < length_code_size; i++) {
box_len <<= 8;
- box_len |= src_data[i];
+ box_len |= size_buf[i];
}
curr_src_len = box_len + length_code_size;
}
dst_box = dst_data + sizeof(start_code);
- src_box = src_data + length_code_size;
memcpy(dst_data, start_code, sizeof(start_code));
- memcpy(dst_box, src_box, box_len);
+ status = CMBlockBufferCopyDataBytes(block,
+ src_offset + length_code_size,
+ box_len,
+ dst_box);
+
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "Cannot copy data: %d\n", status);
+ return AVERROR_EXTERNAL;
+ }
- src_data += curr_src_len;
+ src_offset += curr_src_len;
dst_data += curr_dst_len;
remaining_src_size -= curr_src_len;
int status;
bool is_key_frame;
bool add_header;
- char *buf_data;
size_t length_code_size;
size_t header_size = 0;
size_t in_buf_size;
+ size_t out_buf_size;
int64_t dts_delta;
int64_t time_base_num;
+ int nalu_count;
CMTime pts;
CMTime dts;
-
- CMBlockBufferRef block;
CMVideoFormatDescriptionRef vid_fmt;
vid_fmt = CMSampleBufferGetFormatDescription(sample_buffer);
if (!vid_fmt) {
av_log(avctx, AV_LOG_ERROR, "Cannot get format description.\n");
+ return AVERROR_EXTERNAL;
}
int status = get_params_size(avctx, vid_fmt, &header_size);
if (status) return status;
}
- block = CMSampleBufferGetDataBuffer(sample_buffer);
- if (!block) {
- av_log(avctx, AV_LOG_ERROR, "Could not get block buffer from sample buffer.\n");
- return AVERROR_EXTERNAL;
- }
-
-
- status = CMBlockBufferGetDataPointer(block, 0, &in_buf_size, NULL, &buf_data);
- if (status) {
- av_log(avctx, AV_LOG_ERROR, "Error: cannot get data pointer: %d\n", status);
- return AVERROR_EXTERNAL;
- }
-
- size_t out_buf_size = header_size + in_buf_size;
- bool can_reuse_cmbuffer = !add_header &&
- !pkt->data &&
- length_code_size == sizeof(start_code);
-
- av_init_packet(pkt);
-
- if (can_reuse_cmbuffer) {
- AVBufferRef* buf_ref = av_buffer_create(
- buf_data,
- out_buf_size,
- vtenc_free_block,
- block,
- 0
- );
-
- if (!buf_ref) return AVERROR(ENOMEM);
-
- CFRetain(block);
+ status = count_nalus(length_code_size, sample_buffer, &nalu_count);
+ if(status)
+ return status;
- pkt->buf = buf_ref;
- pkt->data = buf_data;
- pkt->size = in_buf_size;
+ in_buf_size = CMSampleBufferGetTotalSampleSize(sample_buffer);
+ out_buf_size = header_size +
+ in_buf_size +
+ nalu_count * ((int)sizeof(start_code) - (int)length_code_size);
- status = replace_length_codes(length_code_size, pkt->data, pkt->size);
- if (status) {
- av_log(avctx, AV_LOG_ERROR, "Error replacing length codes: %d\n", status);
- return status;
- }
- } else {
- if (!pkt->data) {
- status = av_new_packet(pkt, out_buf_size);
- if(status) return status;
- }
-
- if (pkt->size < out_buf_size) {
- av_log(avctx, AV_LOG_ERROR, "Error: packet's buffer is too small.\n");
- return AVERROR_BUFFER_TOO_SMALL;
- }
+ status = ff_alloc_packet2(avctx, pkt, out_buf_size, out_buf_size);
+ if (status < 0)
+ return status;
- if (add_header) {
- status = copy_param_sets(avctx, vid_fmt, pkt->data, out_buf_size);
- if(status) return status;
- }
+ if (add_header) {
+ status = copy_param_sets(avctx, vid_fmt, pkt->data, out_buf_size);
+ if(status) return status;
+ }
- status = copy_replace_length_codes(
- length_code_size,
- buf_data,
- in_buf_size,
- pkt->data + header_size,
- pkt->size - header_size
- );
+ status = copy_replace_length_codes(
+ avctx,
+ length_code_size,
+ sample_buffer,
+ pkt->data + header_size,
+ pkt->size - header_size
+ );
- if (status) {
- av_log(avctx, AV_LOG_ERROR, "Error copying packet data: %d", status);
- return status;
- }
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "Error copying packet data: %d", status);
+ return status;
}
if (is_key_frame) {
time_base_num = avctx->time_base.num;
pkt->pts = pts.value / time_base_num;
pkt->dts = dts.value / time_base_num - dts_delta;
+ pkt->size = out_buf_size;
return 0;
}
return status;
}
+static int vtenc_populate_extradata(AVCodecContext *avctx,
+ CMVideoCodecType codec_type,
+ CFStringRef profile_level,
+ CFNumberRef gamma_level,
+ CFDictionaryRef enc_info,
+ CFDictionaryRef pixel_buffer_info)
+{
+ VTEncContext *vtctx = avctx->priv_data;
+ AVFrame *frame = av_frame_alloc();
+ int y_size = avctx->width * avctx->height;
+ int chroma_size = (avctx->width / 2) * (avctx->height / 2);
+ CMSampleBufferRef buf = NULL;
+ int status;
+
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ frame->buf[0] = av_buffer_alloc(y_size + 2 * chroma_size);
+
+ if(!frame->buf[0]){
+ status = AVERROR(ENOMEM);
+ goto pe_cleanup;
+ }
+
+ status = vtenc_create_encoder(avctx,
+ codec_type,
+ profile_level,
+ gamma_level,
+ enc_info,
+ pixel_buffer_info,
+ &vtctx->session);
+ if (status)
+ goto pe_cleanup;
+
+ frame->data[0] = frame->buf[0]->data;
+ memset(frame->data[0], 0, y_size);
+
+ frame->data[1] = frame->buf[0]->data + y_size;
+ memset(frame->data[1], 128, chroma_size);
+
+
+ if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) {
+ frame->data[2] = frame->buf[0]->data + y_size + chroma_size;
+ memset(frame->data[2], 128, chroma_size);
+ }
+
+ frame->linesize[0] = avctx->width;
+
+ if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) {
+ frame->linesize[1] =
+ frame->linesize[2] = (avctx->width + 1) / 2;
+ } else {
+ frame->linesize[1] = (avctx->width + 1) / 2;
+ }
+
+ frame->format = avctx->pix_fmt;
+ frame->width = avctx->width;
+ frame->height = avctx->height;
+ av_frame_set_colorspace(frame, avctx->colorspace);
+ av_frame_set_color_range(frame, avctx->color_range);
+ frame->color_trc = avctx->color_trc;
+ frame->color_primaries = avctx->color_primaries;
+
+ frame->pts = 0;
+ status = vtenc_send_frame(avctx, vtctx, frame);
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "Error sending frame: %d\n", status);
+ goto pe_cleanup;
+ }
+
+ //Populates extradata - output frames are flushed and param sets are available.
+ status = VTCompressionSessionCompleteFrames(vtctx->session,
+ kCMTimeIndefinite);
+
+ if (status)
+ goto pe_cleanup;
+
+ status = vtenc_q_pop(vtctx, 0, &buf);
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "popping: %d\n", status);
+ goto pe_cleanup;
+ }
+
+ CFRelease(buf);
+
+
+
+pe_cleanup:
+ if(vtctx->session)
+ CFRelease(vtctx->session);
+
+ vtctx->session = NULL;
+ vtctx->frame_ct_out = 0;
+
+ av_frame_unref(frame);
+ av_frame_free(&frame);
+
+ av_assert0(status != 0 || (avctx->extradata && avctx->extradata_size > 0));
+
+ return status;
+}
+
static av_cold int vtenc_close(AVCodecContext *avctx)
{
VTEncContext *vtctx = avctx->priv_data;