标签:
QuickSync encoder via VideoToolbox
Implements a new encoder in HandBrake that uses VideoToolbox.framework to access the hardware h.264 encoder.
Showing
| @@ -185,15 +185,16 @@ hb_encoder_t *hb_video_encoders_last_item = NULL; |
| hb_encoder_internal_t hb_video_encoders[] = |
| { |
| // legacy encoders, back to HB 0.9.4 whenever possible (disabled) |
| - { { "FFmpeg", "ffmpeg", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG4, }, |
| - { { "MPEG-4 (FFmpeg)", "ffmpeg4", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG4, }, |
| - { { "MPEG-2 (FFmpeg)", "ffmpeg2", HB_VCODEC_FFMPEG_MPEG2, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG2, }, |
| - { { "VP3 (Theora)", "libtheora", HB_VCODEC_THEORA, HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_THEORA, }, |
| + { { "FFmpeg", "ffmpeg", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG4, }, |
| + { { "MPEG-4 (FFmpeg)", "ffmpeg4", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG4, }, |
| + { { "MPEG-2 (FFmpeg)", "ffmpeg2", HB_VCODEC_FFMPEG_MPEG2, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_MPEG2, }, |
| + { { "VP3 (Theora)", "libtheora", HB_VCODEC_THEORA, HB_MUX_MASK_MKV, }, NULL, 0, HB_GID_VCODEC_THEORA, }, |
| // actual encoders |
| - { { "H.264 (x264)", "x264", HB_VCODEC_X264, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264, }, |
| - { { "MPEG-4", "mpeg4", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG4, }, |
| - { { "MPEG-2", "mpeg2", HB_VCODEC_FFMPEG_MPEG2, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG2, }, |
| - { { "Theora", "theora", HB_VCODEC_THEORA, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_THEORA, }, |
| + { { "H.264 (x264)", "x264", HB_VCODEC_X264, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264, }, |
| + { { "H.264 (VideoToolbox)", "h264", HB_VCODEC_VT_H264, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264, }, |
| + { { "MPEG-4", "mpeg4", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG4, }, |
| + { { "MPEG-2", "mpeg2", HB_VCODEC_FFMPEG_MPEG2, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG2, }, |
| + { { "Theora", "theora", HB_VCODEC_THEORA, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_THEORA, }, |
| }; |
| int hb_video_encoders_count = sizeof(hb_video_encoders) / sizeof(hb_video_encoders[0]); |
| static int hb_video_encoder_is_enabled(int encoder) |
| @@ -202,6 +203,7 @@ static int hb_video_encoder_is_enabled(int encoder) |
| { |
| // the following encoders are always enabled |
| case HB_VCODEC_X264: |
| + case HB_VCODEC_VT_H264: |
| case HB_VCODEC_THEORA: |
| case HB_VCODEC_FFMPEG_MPEG4: |
| case HB_VCODEC_FFMPEG_MPEG2: |
| @@ -403,6 +403,7 @@ struct hb_job_s |
| #define HB_VCODEC_MASK 0x00000FF |
| #define HB_VCODEC_X264 0x0000001 |
| #define HB_VCODEC_THEORA 0x0000002 |
| +#define HB_VCODEC_VT_H264 0x0000004 |
| #define HB_VCODEC_FFMPEG_MPEG4 0x0000010 |
| #define HB_VCODEC_FFMPEG_MPEG2 0x0000020 |
| #define HB_VCODEC_FFMPEG_MASK 0x00000F0 |
| @@ -1008,6 +1009,7 @@ extern hb_work_object_t hb_encvorbis; |
| extern hb_work_object_t hb_muxer; |
| extern hb_work_object_t hb_encca_aac; |
| extern hb_work_object_t hb_encca_haac; |
| +extern hb_work_object_t hb_encvt_h264; |
| extern hb_work_object_t hb_encavcodeca; |
| extern hb_work_object_t hb_reader; |
| |
| @@ -1644,6 +1644,7 @@ int hb_global_init() |
| #ifdef __APPLE__ |
| hb_register(&hb_encca_aac); |
| hb_register(&hb_encca_haac); |
| + hb_register(&hb_encvt_h264); |
| #endif |
| #ifdef USE_FAAC |
| hb_register(&hb_encfaac); |
| @@ -406,6 +406,7 @@ enum |
| WORK_ENCAVCODEC, |
| WORK_ENCX264, |
| WORK_ENCTHEORA, |
| + WORK_ENCVT_H264, |
| WORK_DECA52, |
| WORK_DECAVCODEC, |
| WORK_DECAVCODECV, |
| @@ -97,6 +97,7 @@ static int MKVInit( hb_mux_object_t * m ) |
| switch (job->vcodec) |
| { |
| case HB_VCODEC_X264: |
| + case HB_VCODEC_VT_H264: |
| track->codecID = MK_VCODEC_MP4AVC; |
| /* Taken from x264 muxers.c */ |
| avcC_len = 5 + 1 + 2 + job->config.h264.sps_length + 1 + 2 + job->config.h264.pps_length; |
| @@ -129,7 +129,7 @@ static int MP4Init( hb_mux_object_t * m ) |
| return 0; |
| } |
|
|
| - if( job->vcodec == HB_VCODEC_X264 ) |
| + if( job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_VT_H264 ) |
| { |
| /* Stolen from mp4creator */ |
| MP4SetVideoProfileLevel( m->file, 0x7F ); |
| @@ -684,7 +684,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, |
| if( mux_data == job->mux_data ) |
| { |
| /* Video */ |
| - if( job->vcodec == HB_VCODEC_X264 || |
| + if( job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_VT_H264 || |
| ( job->vcodec & HB_VCODEC_FFMPEG_MASK ) ) |
| { |
| if ( buf && buf->s.start < buf->s.renderOffset ) |
| @@ -706,7 +706,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, |
|
|
| stop = buf->s.start + buf->s.duration; |
|
|
| - if( job->vcodec == HB_VCODEC_X264 || |
| + if( job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_VT_H264 || |
| ( job->vcodec & HB_VCODEC_FFMPEG_MASK ) ) |
| { |
| // x264 supplies us with DTS, so offset is PTS - DTS |
| @@ -744,7 +744,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, |
| } |
| } |
|
|
| - if( job->vcodec == HB_VCODEC_X264 || |
| + if( job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_VT_H264 || |
| ( job->vcodec & HB_VCODEC_FFMPEG_MASK ) ) |
| { |
| // x264 supplies us with DTS |
| @@ -822,7 +822,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, |
| } |
|
|
| /* Here‘s where the sample actually gets muxed. */ |
| - if( ( job->vcodec == HB_VCODEC_X264 || |
| + if( ( job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_VT_H264|| |
| ( job->vcodec & HB_VCODEC_FFMPEG_MASK ) ) |
| && mux_data == job->mux_data ) |
| { |
| @@ -0,0 +1,498 @@ |
| +/* encvt_h264.c |
| + |
| + Copyright (c) 2003-2013 HandBrake Team |
| + This file is part of the HandBrake source code |
| + Homepage: <http://handbrake.fr/>. |
| + It may be used under the terms of the GNU General Public License v2. |
| + For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html |
| + */ |
| + |
| +#include "hb.h" |
| +#include <VideoToolbox/VideoToolbox.h> |
| +#include <CoreMedia/CoreMedia.h> |
| +#include <CoreVideo/CoreVideo.h> |
| + |
| +int encvt_h264Init( hb_work_object_t *, hb_job_t * ); |
| +int encvt_h264Work( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** ); |
| +void encvt_h264Close( hb_work_object_t * ); |
| + |
| +hb_work_object_t hb_encvt_h264 = |
| +{ |
| + WORK_ENCVT_H264, |
| + "H.264 encoder (VideoToolbox)", |
| + encvt_h264Init, |
| + encvt_h264Work, |
| + encvt_h264Close |
| +}; |
| + |
| +struct hb_work_private_s |
| +{ |
| + hb_job_t * job; |
| + |
| + VTCompressionSessionRef session; |
| + CMSimpleQueueRef queue; |
| + |
| + int chap_mark; // saved chap mark when we‘re propagating it |
| + int64_t next_chap; |
| +}; |
| + |
| +void pixelBufferReleasePlanarBytesCallback( void *releaseRefCon, const void *dataPtr, size_t dataSize, size_t numberOfPlanes, const void *planeAddresses[] ) |
| +{ |
| + hb_buffer_t * buf = (hb_buffer_t *) releaseRefCon; |
| + hb_buffer_close( &buf ); |
| +} |
| + |
| +void pixelBufferReleaseBytesCallback( void *releaseRefCon, const void *baseAddress ) |
| +{ |
| + free( (void *) baseAddress ); |
| +} |
| + |
| +void myVTCompressionOutputCallback( |
| +void *outputCallbackRefCon, |
| +void *sourceFrameRefCon, |
| +OSStatus status, |
| +VTEncodeInfoFlags infoFlags, |
| +CMSampleBufferRef sampleBuffer ) |
| +{ |
| + OSStatus err; |
| + |
| + if (sourceFrameRefCon) |
| + { |
| + CVPixelBufferRef pixelbuffer = sourceFrameRefCon; |
| + CVPixelBufferRelease(pixelbuffer); |
| + } |
| + |
| + if (status != noErr) |
| + { |
| + hb_log("VTCompressionSession: myVTCompressionOutputCallback called error"); |
| + } |
| + else |
| + { |
| + CFRetain(sampleBuffer); |
| + CMSimpleQueueRef queue = outputCallbackRefCon; |
| + err = CMSimpleQueueEnqueue(queue, sampleBuffer); |
| + if (err) |
| + hb_log("VTCompressionSession: myVTCompressionOutputCallback queue full"); |
| + } |
| +} |
| + |
| +OSStatus initVTSession(hb_work_object_t * w, hb_job_t * job, hb_work_private_t * pv) |
| +{ |
| + OSStatus err = noErr; |
| + |
| + CFStringRef key = kVTVideoEncoderSpecification_EncoderID; |
| + CFStringRef value = CFSTR("com.apple.videotoolbox.videoencoder.h264.gva"); |
| + |
| + CFStringRef bkey = CFSTR("EnableHardwareAcceleratedVideoEncoder"); |
| + CFBooleanRef bvalue = kCFBooleanTrue; |
| + |
| + CFStringRef ckey = CFSTR("RequireHardwareAcceleratedVideoEncoder"); |
| + CFBooleanRef cvalue = kCFBooleanTrue; |
| + |
| + CFMutableDictionaryRef encoderSpecifications = CFDictionaryCreateMutable( |
| + kCFAllocatorDefault, |
| + 3, |
| + &kCFTypeDictionaryKeyCallBacks, |
| + &kCFTypeDictionaryValueCallBacks); |
| + |
| + // Comment out to disable QuickSync |
| + CFDictionaryAddValue(encoderSpecifications, bkey, bvalue); |
| + CFDictionaryAddValue(encoderSpecifications, ckey, cvalue); |
| + CFDictionaryAddValue(encoderSpecifications, key, value); |
| + |
| + err = VTCompressionSessionCreate( |
| + kCFAllocatorDefault, |
| + job->width, |
| + job->height, |
| + kCMVideoCodecType_H264, |
| + encoderSpecifications, |
| + NULL, |
| + NULL, |
| + &myVTCompressionOutputCallback, |
| + pv->queue, |
| + &pv->session); |
| + |
| + if (err != noErr) |
| + { |
| + hb_log("Error creating a VTCompressionSession err=%"PRId64"", (int64_t)err); |
| + return err; |
| + } |
| + |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanTrue); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_AllowFrameReordering failed"); |
| + |
| + const int maxKeyFrameInterval = 10 * 30; |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_MaxKeyFrameInterval, |
| + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &maxKeyFrameInterval)); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_MaxKeyFrameInterval failed"); |
| + |
| + const int maxFrameDelayCount = 24; |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_MaxFrameDelayCount, |
| + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &maxFrameDelayCount)); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_MaxKeyFrameInterval failed"); |
| + |
| + const int frameRate = (int)( (double)job->vrate / (double)job->vrate_base + 0.5 ); |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_ExpectedFrameRate, |
| + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameRate)); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_ExpectedFrameRate failed"); |
| + |
| + if( job->vquality < 0 ) |
| + { |
| + const int averageBitRate = job->vbitrate * 1024; |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_AverageBitRate, |
| + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &averageBitRate)); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_AverageBitRate failed"); |
| + } |
| + |
| + err = VTSessionSetProperty(pv->session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_5_0); |
| + if (err != noErr) |
| + hb_log("VTSessionSetProperty: kVTCompressionPropertyKey_ProfileLevel failed"); |
| + |
| + CFRelease(encoderSpecifications); |
| + |
| + return err; |
| +} |
| + |
| +void setupMagicCookie(hb_work_object_t * w, hb_job_t * job, hb_work_private_t * pv) |
| +{ |
| + OSStatus err; |
| + CMFormatDescriptionRef format = NULL; |
| + |
| + err = initVTSession(w, job, pv); |
| + |
| + size_t rgbBufSize = sizeof(uint8) * 3 * job->width * job->height; |
| + uint8 *rgbBuf = malloc(rgbBufSize); |
| + |
| + // Compress a random frame to get the magicCookie |
| + CVPixelBufferRef pxbuffer = NULL; |
| + CVPixelBufferCreateWithBytes(kCFAllocatorDefault, |
| + job->width, |
| + job->height, |
| + kCVPixelFormatType_24RGB, |
| + rgbBuf, |
| + job->width * 3, |
| + &pixelBufferReleaseBytesCallback, |
| + NULL, |
| + NULL, |
| + &pxbuffer); |
| + |
| + if (kCVReturnSuccess != err) |
| + hb_log("VTCompressionSession: CVPixelBuffer error"); |
| + |
| + CMTime pts = CMTimeMake(0, 90000); |
| + err = VTCompressionSessionEncodeFrame( |
| + pv->session, |
| + pxbuffer, |
| + pts, |
| + kCMTimeInvalid, |
| + NULL, |
| + pxbuffer, |
| + NULL); |
| + VTCompressionSessionCompleteFrames(pv->session, CMTimeMake(0,90000)); |
| + |
| + CMSampleBufferRef sampleBuffer = (CMSampleBufferRef) CMSimpleQueueDequeue(pv->queue); |
| + |
| + format = CMSampleBufferGetFormatDescription(sampleBuffer); |
| + if (!format) |
| + hb_log("VTCompressionSession: Format Description error"); |
| + |
| + CFDictionaryRef extentions = CMFormatDescriptionGetExtensions(format); |
| + if (!extentions) |
| + hb_log("VTCompressionSession: Format Description Extensions error"); |
| + |
| + CFDictionaryRef atoms = CFDictionaryGetValue(extentions, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms); |
| + CFDataRef magicCookie = CFDictionaryGetValue(atoms, CFSTR("avcC")); |
| + if (!magicCookie) |
| + hb_log("VTCompressionSession: Magic Cookie error"); |
| + |
| + const uint8_t *avcCAtom = CFDataGetBytePtr(magicCookie); |
| + |
| + SInt64 i; |
| + int8_t spsCount = (avcCAtom[5] & 0x1f); |
| + uint8_t ptrPos = 6; |
| + uint8_t spsPos = 0; |
| + for (i = 0; i < spsCount; i++) { |
| + uint16_t spsSize = (avcCAtom[ptrPos++] << 8) & 0xff00; |
| + spsSize += avcCAtom[ptrPos++] & 0xff; |
| + memcpy(w->config->h264.sps + spsPos, avcCAtom+ptrPos, spsSize);; |
| + ptrPos += spsSize; |
| + spsPos += spsSize; |
| + } |
| + w->config->h264.sps_length = spsPos; |
| + |
| + int8_t ppsCount = avcCAtom[ptrPos++]; |
| + uint8_t ppsPos = 0; |
| + for (i = 0; i < ppsCount; i++) { |
| + uint16_t ppsSize = (avcCAtom[ptrPos++] << 8) & 0xff00; |
| + ppsSize += avcCAtom[ptrPos++] & 0xff; |
| + memcpy(w->config->h264.pps + ppsPos, avcCAtom+ptrPos, ppsSize);; |
| + |
| + ptrPos += ppsSize; |
| + ppsPos += ppsSize; |
| + } |
| + w->config->h264.pps_length = ppsPos; |
| + |
| + VTCompressionSessionInvalidate(pv->session); |
| + CFRelease(pv->session); |
| + CFRelease(sampleBuffer); |
| +} |
| + |
| +int encvt_h264Init( hb_work_object_t * w, hb_job_t * job ) |
| +{ |
| + OSStatus err; |
| + hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) ); |
| + w->private_data = pv; |
| + |
| + pv->job = job; |
| + |
| + CMSimpleQueueCreate( |
| + kCFAllocatorDefault, |
| + 200, |
| + &pv->queue); |
| + |
| + setupMagicCookie(w, job, pv); |
| + |
| + err = initVTSession(w, job, pv); |
| + if (err != noErr) |
| + { |
| + hb_log("Error creating a VTCompressionSession err=%"PRId64"", (int64_t)err); |
| + *job->die = 1; |
| + return -1; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +/*********************************************************************** |
| + * Close |
| + *********************************************************************** |
| + * |
| + **********************************************************************/ |
| +void encvt_h264Close( hb_work_object_t * w ) |
| +{ |
| + hb_work_private_t * pv = w->private_data; |
| + |
| + VTCompressionSessionInvalidate(pv->session); |
| + CFRelease(pv->session); |
| + CFRelease(pv->queue); |
| + |
| + free( pv ); |
| + w->private_data = NULL; |
| +} |
| + |
| +/*********************************************************************** |
| + * Work |
| + *********************************************************************** |
| + * |
| + **********************************************************************/ |
| + |
| +hb_buffer_t* extractData(CMSampleBufferRef sampleBuffer, hb_work_object_t * w) |
| +{ |
| + OSStatus err; |
| + hb_work_private_t * pv = w->private_data; |
| + hb_job_t * job = pv->job; |
| + hb_buffer_t *buf = NULL; |
| + |
| + CMItemCount samplesNum = CMSampleBufferGetNumSamples(sampleBuffer); |
| + if (samplesNum > 1) |
| + hb_log("VTCompressionSession: more than 1 sample in sampleBuffer = %ld", samplesNum); |
| + |
| + CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(sampleBuffer); |
| + if (buffer) |
| + { |
| + size_t sampleSize = CMBlockBufferGetDataLength(buffer); |
| + buf = hb_buffer_init( sampleSize ); |
| + |
| + err = CMBlockBufferCopyDataBytes(buffer, 0, sampleSize, buf->data); |
| + |
| + if (err != kCMBlockBufferNoErr) |
| + hb_log("VTCompressionSession: CMBlockBufferCopyDataBytes error"); |
| + |
| + buf->s.frametype = HB_FRAME_IDR; |
| + buf->s.flags |= HB_FRAME_REF; |
| + |
| + CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0); |
| + if (CFArrayGetCount(attachmentsArray)) |
| + { |
| + CFDictionaryRef dict = CFArrayGetValueAtIndex(attachmentsArray, 0); |
| + if (CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, NULL)) |
| + { |
| + CFBooleanRef b; |
| + if (CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_PartialSync, NULL)) |
| + { |
| + buf->s.frametype = HB_FRAME_I; |
| + } |
| + else if (CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_IsDependedOnByOthers,(const void **) &b)) |
| + { |
| + Boolean bv = CFBooleanGetValue(b); |
| + if (bv) |
| + buf->s.frametype = HB_FRAME_P; |
| + else |
| + { |
| + buf->s.frametype = HB_FRAME_B; |
| + buf->s.flags &= ~HB_FRAME_REF; |
| + } |
| + } |
| + else { |
| + buf->s.frametype = HB_FRAME_P; |
| + } |
| + } |
| + } |
| + |
| + CMTime decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer); |
| + CMTime presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); |
| + |
| + if ( !w->config->h264.init_delay && presentationTimeStamp.value ) |
| + { |
| + w->config->h264.init_delay = presentationTimeStamp.value; |
| + } |
| + |
| + buf->f.width = job->width; |
| + buf->f.height = job->height; |
| + buf->s.start = presentationTimeStamp.value + w->config->h264.init_delay; |
| + buf->s.stop = presentationTimeStamp.value + w->config->h264.init_delay; |
| + buf->s.renderOffset = decodeTimeStamp.value; |
| + |
| + /* if we have a chapter marker pending and this |
| + frame‘s presentation time stamp is at or after |
| + the marker‘s time stamp, use this as the |
| + chapter start. */ |
| + if( buf->s.frametype == HB_FRAME_IDR && pv->next_chap != 0 && pv->next_chap <= presentationTimeStamp.value ) |
| + { |
| + pv->next_chap = 0; |
| + buf->s.new_chap = pv->chap_mark; |
| + } |
| + } |
| + |
| + return buf; |
| +} |
| + |
| +int encvt_h264Work( hb_work_object_t * w, hb_buffer_t ** buf_in, |
| + hb_buffer_t ** buf_out ) |
| +{ |
| + hb_work_private_t * pv = w->private_data; |
| + hb_job_t * job = pv->job; |
| + hb_buffer_t * in = *buf_in, * buf = NULL; |
| + CMSampleBufferRef sampleBuffer = NULL; |
| + |
| + OSStatus err; |
| + |
| + if ( in->size <= 0 ) |
| + { |
| + // EOF on input. Flush any frames still in the decoder then |
| + // send the eof downstream to tell the muxer we‘re done. |
| + VTCompressionSessionCompleteFrames(pv->session, kCMTimeInvalid); |
| + |
| + hb_buffer_t *last_buf = NULL; |
| + |
| + while ( ( sampleBuffer = (CMSampleBufferRef) CMSimpleQueueDequeue(pv->queue) ) ) |
| + { |
| + buf = extractData(sampleBuffer, w); |
| + CFRelease(sampleBuffer); |
| + |
| + if ( buf ) |
| + { |
| + if ( last_buf == NULL ) |
| + *buf_out = buf; |
| + else |
| + last_buf->next = buf; |
| + last_buf = buf; |
| + } |
| + } |
| + |
| + // Flushed everything - add the eof to the end of the chain. |
| + if ( last_buf == NULL ) |
| + *buf_out = in; |
| + else |
| + last_buf->next = in; |
| + |
| + *buf_in = NULL; |
| + return HB_WORK_DONE; |
| + } |
| + |
| + // Create a CVPixelBuffer to wrap the frame data |
| + CVPixelBufferRef pxbuffer = NULL; |
| + |
| + hb_buffer_t *in_c = hb_buffer_dup(in); |
| + void *planeBaseAddress[3] = {in_c->plane[0].data, in_c->plane[1].data, in_c->plane[2].data}; |
| + size_t planeWidth[3] = {in_c->plane[0].width, in_c->plane[1].width, in_c->plane[2].width}; |
| + size_t planeHeight[3] = {in_c->plane[0].height, in_c->plane[1].height, in_c->plane[2].height}; |
| + size_t planeBytesPerRow[3] = {in_c->plane[0].stride, in_c->plane[1].stride, in_c->plane[2].stride}; |
| + |
| + err = CVPixelBufferCreateWithPlanarBytes( |
| + kCFAllocatorDefault, |
| + job->width, |
| + job->height, |
| + kCVPixelFormatType_420YpCbCr8Planar, |
| + in_c->data, |
| + 0, |
| + 3, |
| + planeBaseAddress, |
| + planeWidth, |
| + planeHeight, |
| + planeBytesPerRow, |
| + &pixelBufferReleasePlanarBytesCallback, |
| + in_c, |
| + NULL, |
| + &pxbuffer); |
| + |
| + if (kCVReturnSuccess != err) |
| + hb_log("VTCompressionSession: CVPixelBuffer error"); |
| + |
| + CFDictionaryRef frameProperties = NULL; |
| + if( in->s.new_chap && job->chapter_markers ) |
| + { |
| + /* chapters have to start with an IDR frame so request that this |
| + frame be coded as IDR. Since there may be up to 16 frames |
| + currently buffered in the encoder remember the timestamp so |
| + when this frame finally pops out of the encoder we‘ll mark |
| + its buffer as the start of a chapter. */ |
| + const void *keys[1] = { kVTEncodeFrameOptionKey_ForceKeyFrame }; |
| + const void *values[1] = { kCFBooleanTrue }; |
| + |
| + frameProperties = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| + |
| + if( pv->next_chap == 0 ) |
| + { |
| + pv->next_chap = in->s.start; |
| + pv->chap_mark = in->s.new_chap; |
| + } |
| + /* don‘t let ‘work_loop‘ put a chapter mark on the wrong buffer */ |
| + in->s.new_chap = 0; |
| + } |
| + |
| + // Send the frame to be encoded |
| + err = VTCompressionSessionEncodeFrame( |
| + pv->session, |
| + pxbuffer, |
| + CMTimeMake(in->s.start, 90000), |
| + kCMTimeInvalid, |
| + frameProperties, |
| + pxbuffer, |
| + NULL); |
| + |
| + if (err) |
| + hb_log("VTCompressionSession: VTCompressionSessionEncodeFrame error"); |
| + |
| + if (frameProperties) |
| + CFRelease(frameProperties); |
| + |
| + // Return a frame if ready |
| + sampleBuffer = (CMSampleBufferRef) CMSimpleQueueDequeue(pv->queue); |
| + |
| + if (sampleBuffer) |
| + { |
| + buf = extractData(sampleBuffer, w); |
| + CFRelease(sampleBuffer); |
| + } |
| + |
| + *buf_out = buf; |
| + |
| + return HB_WORK_OK; |
| +} |
| + |
| @@ -1027,6 +1027,9 @@ static void do_job(hb_job_t *job) |
| case HB_VCODEC_X264: |
| w = hb_get_work( WORK_ENCX264 ); |
| break; |
| + case HB_VCODEC_VT_H264: |
| + w = hb_get_work( WORK_ENCVT_H264 ); |
| + break; |
| case HB_VCODEC_THEORA: |
| w = hb_get_work( WORK_ENCTHEORA ); |
| break; |
| @@ -120,6 +120,12 @@ |
| 27D6C77314B102DA00B785E4 /* libxml2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C74014B102DA00B785E4 /* libxml2.a */; }; |
| 3490BCB41614CF8D002A5AD7 /* HandBrake.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3490BCB31614CF8D002A5AD7 /* HandBrake.icns */; }; |
| 46AB433515F98A2B009C0961 /* DockTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 46AB433415F98A2B009C0961 /* DockTextField.m */; }; |
| + A90156C21770AB9200079F5A /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A90156C11770AB9200079F5A /* VideoToolbox.framework */; }; |
| + A90156C31770ADCE00079F5A /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A90156C11770AB9200079F5A /* VideoToolbox.framework */; }; |
| + A94BC59D1770C0110055F56B /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A94BC59C1770C0110055F56B /* CoreMedia.framework */; }; |
| + A94BC59E1770C0180055F56B /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A94BC59C1770C0110055F56B /* CoreMedia.framework */; }; |
| + A94BC5A01770C0C00055F56B /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A94BC59F1770C0C00055F56B /* CoreVideo.framework */; }; |
| + A94BC5A11770C0C70055F56B /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A94BC59F1770C0C00055F56B /* CoreVideo.framework */; }; |
| A9E1467B16BC2ABD00C307BC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9E1467A16BC2ABD00C307BC /* QuartzCore.framework */; }; |
| A9E1468016BC2AD800C307BC /* next-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467C16BC2AD800C307BC /* next-p.pdf */; }; |
| A9E1468116BC2AD800C307BC /* pause-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467D16BC2AD800C307BC /* pause-p.pdf */; }; |
| @@ -305,6 +311,9 @@ |
| 34FF2FC014EEC363004C2400 /* HBAdvancedController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAdvancedController.h; sourceTree = "<group>"; }; |
| 46AB433315F98A2B009C0961 /* DockTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DockTextField.h; sourceTree = "<group>"; }; |
| 46AB433415F98A2B009C0961 /* DockTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DockTextField.m; sourceTree = "<group>"; }; |
| + A90156C11770AB9200079F5A /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = /System/Library/Frameworks/VideoToolbox.framework; sourceTree = "<absolute>"; }; |
| + A94BC59C1770C0110055F56B /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = /System/Library/Frameworks/CoreMedia.framework; sourceTree = "<absolute>"; }; |
| + A94BC59F1770C0C00055F56B /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = /System/Library/Frameworks/CoreVideo.framework; sourceTree = "<absolute>"; }; |
| A9E1467A16BC2ABD00C307BC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = "<absolute>"; }; |
| A9E1467C16BC2AD800C307BC /* next-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "next-p.pdf"; sourceTree = "<group>"; }; |
| A9E1467D16BC2AD800C307BC /* pause-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "pause-p.pdf"; sourceTree = "<group>"; }; |
| @@ -343,6 +352,9 @@ |
| isa = PBXFrameworksBuildPhase; |
| buildActionMask = 2147483647; |
| files = ( |
| + A94BC5A11770C0C70055F56B /* CoreVideo.framework in Frameworks */, |
| + A94BC59D1770C0110055F56B /* CoreMedia.framework in Frameworks */, |
| + A90156C31770ADCE00079F5A /* VideoToolbox.framework in Frameworks */, |
| 273F203014ADB9790021BE6D /* AudioToolbox.framework in Frameworks */, |
| 273F202314ADB8650021BE6D /* IOKit.framework in Frameworks */, |
| 273F203314ADB9F00021BE6D /* CoreServices.framework in Frameworks */, |
| @@ -379,6 +391,9 @@ |
| isa = PBXFrameworksBuildPhase; |
| buildActionMask = 2147483647; |
| files = ( |
| + A94BC5A01770C0C00055F56B /* CoreVideo.framework in Frameworks */, |
| + A94BC59E1770C0180055F56B /* CoreMedia.framework in Frameworks */, |
| + A90156C21770AB9200079F5A /* VideoToolbox.framework in Frameworks */, |
| A9E1467B16BC2ABD00C307BC /* QuartzCore.framework in Frameworks */, |
| 273F21C114ADE7A20021BE6D /* Growl.framework in Frameworks */, |
| 273F21C214ADE7BC0021BE6D /* Sparkle.framework in Frameworks */, |
| @@ -495,6 +510,9 @@ |
| 273F203414ADBAC30021BE6D /* Frameworks */ = { |
| isa = PBXGroup; |
| children = ( |
| + A94BC59F1770C0C00055F56B /* CoreVideo.framework */, |
| + A94BC59C1770C0110055F56B /* CoreMedia.framework */, |
| + A90156C11770AB9200079F5A /* VideoToolbox.framework */, |
| A9E1467A16BC2ABD00C307BC /* QuartzCore.framework */, |
| 273F20C714ADC4FF0021BE6D /* QTKit.framework */, |
| 273F202F14ADB9790021BE6D /* AudioToolbox.framework */, |
| @@ -50,7 +50,7 @@ BUILD.out += $(TEST.install.exe) |
| TEST.GCC.I += $(LIBHB.GCC.I) |
|
|
| ifeq ($(BUILD.system),darwin) |
| - TEST.GCC.f += IOKit CoreServices AudioToolbox |
| + TEST.GCC.f += IOKit CoreServices AudioToolbox VideoToolbox CoreMedia CoreVideo |
| TEST.GCC.l += iconv |
| else ifeq ($(BUILD.system),linux) |
| TEST.GCC.l += pthread dl m |
Attach files by dragging & dropping or selecting them.
HandBrake-QuickSync-Mac (内容:QuickSync encoder via VideoToolbox )
标签:
原文地址:http://www.cnblogs.com/sunminmin/p/4976409.html