Что такое 10.6-совместимое средство записи видеокадров в фильм без использования QuickTime API? - PullRequest
13 голосов
/ 22 июля 2011

Я обновляю приложение, чтобы оно было 64-битно-совместимым, но у меня возникли небольшие трудности с нашим кодом записи видеороликов.У нас есть камера FireWire, которая подает кадры YUV в наше приложение, которое мы обрабатываем и кодируем на диск в фильме MPEG4.В настоящее время мы используем API QuickTime на основе C (для этого используется диспетчер сжатия изображений и т. Д.), Но старый API QuickTime не поддерживает 64-разрядную версию.

Моей первой попыткой было использование QTKit.QTMovie и кодировать отдельные кадры, используя -addImage:forDuration:withAttributes:, но для этого требуется создание NSImage для каждого кадра (что дорого в вычислительном отношении), и он не выполняет временное сжатие , поэтому он не генерирует наиболее компактныефайлы.

Я хотел бы использовать что-то вроде QTCaptureMovieFileOutput QTKit Capture, но я не могу понять, как подавать необработанные кадры в то, что не связано с QTCaptureInput.Мы не можем использовать нашу камеру напрямую с QTKit Capture, потому что нам нужно вручную контролировать ее усиление, экспозицию и т. Д.

На Lion у нас теперь есть класс AVAssetWriter в AVFoundation, который позволяет вам делать это, но мне все еще нужно нацелиться на Snow Leopard, поэтому я пытаюсь найти решение, которое также работает там.

Следовательно, есть ли способ сделать не-QuickTime покадровымзапись видео по кадрам, которая более эффективна, чем -addImage:forDuration:withAttributes: в QTMovie, и обеспечивает размеры файлов, сопоставимые с возможностями более старого API QuickTime?

Ответы [ 3 ]

10 голосов
/ 22 октября 2011

В конце концов, я решил использовать подход, предложенный TiansHUo, и использовать libavcodec для сжатия видео здесь. Основываясь на инструкциях Мартина здесь , я загрузил исходный код FFmpeg и создал 64-битную совместимую версию необходимых библиотек, используя

./configure --disable-gpl --arch=x86_64 --cpu=core2 --enable-shared --disable-amd3dnow --enable-memalign-hack --cc=llvm-gcc
make
sudo make install

Это создает разделяемые библиотеки LGPL для 64-битных процессоров Core2 в Mac. К сожалению, я еще не нашел способ заставить библиотеку работать без сбоев при включенной оптимизации MMX, так что сейчас она отключена. Это несколько замедляет кодирование. После некоторых экспериментов я обнаружил, что могу создать 64-разрядную версию библиотеки, в которой включена оптимизация MMX и которая стабильна на Mac, используя указанные выше параметры конфигурации. Это намного быстрее при кодировании, чем библиотека, построенная с отключенным MMX.

Обратите внимание, что если вы используете эти общие библиотеки, вам следует обязательно следовать инструкциям LGPL *1013* на сайте FFmpeg к письму.

Чтобы эти общие библиотеки работали должным образом при размещении в нужной папке в моем комплекте приложений Mac, мне нужно было использовать install_name_tool, чтобы настроить внутренние пути поиска в этих библиотеках так, чтобы они указывали на их новое местоположение в каталоге Frameworks. в комплекте приложения:

install_name_tool -id @executable_path/../Frameworks/libavutil.51.9.1.dylib libavutil.51.9.1.dylib

install_name_tool -id @executable_path/../Frameworks/libavcodec.53.7.0.dylib libavcodec.53.7.0.dylib
install_name_tool -change /usr/local/lib/libavutil.dylib @executable_path/../Frameworks/libavutil.51.9.1.dylib libavcodec.53.7.0.dylib

install_name_tool -id @executable_path/../Frameworks/libavformat.53.4.0.dylib libavformat.53.4.0.dylib
install_name_tool -change /usr/local/lib/libavutil.dylib @executable_path/../Frameworks/libavutil.51.9.1.dylib libavformat.53.4.0.dylib
install_name_tool -change /usr/local/lib/libavcodec.dylib @executable_path/../Frameworks/libavcodec.53.7.0.dylib libavformat.53.4.0.dylib

install_name_tool -id @executable_path/../Frameworks/libswscale.2.0.0.dylib libswscale.2.0.0.dylib
install_name_tool -change /usr/local/lib/libavutil.dylib @executable_path/../Frameworks/libavutil.51.9.1.dylib libswscale.2.0.0.dylib

Ваши конкретные пути могут отличаться. Эта настройка позволяет им работать из пакета приложения без необходимости устанавливать их в / usr / local / lib в системе пользователя.

Затем я связал свой проект Xcode с этими библиотеками, и я создал отдельный класс для обработки кодирования видео. Этот класс принимает необработанные видеокадры (в формате BGRA) через свойство videoFrameToEncode и кодирует их в файле movieFileName как видео MPEG4 в контейнере MP4. Код выглядит следующим образом:

SPVideoRecorder.h

#import <Foundation/Foundation.h>

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"

uint64_t getNanoseconds(void);

@interface SPVideoRecorder : NSObject
{
    NSString *movieFileName;
    CGFloat framesPerSecond;
    AVCodecContext *codecContext;
    AVStream *videoStream;
    AVOutputFormat *outputFormat;
    AVFormatContext *outputFormatContext;
    AVFrame *videoFrame;
    AVPicture inputRGBAFrame;

    uint8_t *pictureBuffer;
    uint8_t *outputBuffer;
    unsigned int outputBufferSize;
    int frameColorCounter;

    unsigned char *videoFrameToEncode;

    dispatch_queue_t videoRecordingQueue;
    dispatch_semaphore_t frameEncodingSemaphore;
    uint64_t movieStartTime;
}

@property(readwrite, assign) CGFloat framesPerSecond;
@property(readwrite, assign) unsigned char *videoFrameToEncode;
@property(readwrite, copy) NSString *movieFileName;

// Movie recording control
- (void)startRecordingMovie;
- (void)encodeNewFrameToMovie;
- (void)stopRecordingMovie;


@end

SPVideoRecorder.m

#import "SPVideoRecorder.h"
#include <sys/time.h>

@implementation SPVideoRecorder

uint64_t getNanoseconds(void)
{
    struct timeval now;
    gettimeofday(&now, NULL);
    return now.tv_sec * NSEC_PER_SEC + now.tv_usec * NSEC_PER_USEC;
}

#pragma mark -
#pragma mark Initialization and teardown

- (id)init;
{
    if (!(self = [super init]))
    {
        return nil;     
    }

    /* must be called before using avcodec lib */
    avcodec_init();

    /* register all the codecs */
    avcodec_register_all();
    av_register_all();

    av_log_set_level( AV_LOG_ERROR );

    videoRecordingQueue = dispatch_queue_create("com.sonoplot.videoRecordingQueue", NULL);;
    frameEncodingSemaphore = dispatch_semaphore_create(1);

    return self;
}

#pragma mark -
#pragma mark Movie recording control

- (void)startRecordingMovie;
{   
    dispatch_async(videoRecordingQueue, ^{
        NSLog(@"Start recording to file: %@", movieFileName);

        const char *filename = [movieFileName UTF8String];

        // Use an MP4 container, in the standard QuickTime format so it's readable on the Mac
        outputFormat = av_guess_format("mov", NULL, NULL);
        if (!outputFormat) {
            NSLog(@"Could not set output format");
        }

        outputFormatContext = avformat_alloc_context();
        if (!outputFormatContext)
        {
            NSLog(@"avformat_alloc_context Error!");
        }

        outputFormatContext->oformat = outputFormat;
        snprintf(outputFormatContext->filename, sizeof(outputFormatContext->filename), "%s", filename);

        // Add a video stream to the MP4 file 
        videoStream = av_new_stream(outputFormatContext,0);
        if (!videoStream)
        {
            NSLog(@"av_new_stream Error!");
        }


        // Use the MPEG4 encoder (other DiVX-style encoders aren't compatible with this container, and x264 is GPL-licensed)
        AVCodec *codec = avcodec_find_encoder(CODEC_ID_MPEG4);  
        if (!codec) {
            fprintf(stderr, "codec not found\n");
            exit(1);
        }

        codecContext = videoStream->codec;

        codecContext->codec_id = codec->id;
        codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
        codecContext->bit_rate = 4800000;
        codecContext->width = 640;
        codecContext->height = 480;
        codecContext->pix_fmt = PIX_FMT_YUV420P;
//      codecContext->time_base = (AVRational){1,(int)round(framesPerSecond)};
//      videoStream->time_base = (AVRational){1,(int)round(framesPerSecond)};
        codecContext->time_base = (AVRational){1,200}; // Set it to 200 FPS so that we give a little wiggle room when recording at 50 FPS
        videoStream->time_base = (AVRational){1,200};
//      codecContext->max_b_frames = 3;
//      codecContext->b_frame_strategy = 1;
        codecContext->qmin = 1;
        codecContext->qmax = 10;    
//      codecContext->mb_decision = 2; // -mbd 2
//      codecContext->me_cmp = 2; // -cmp 2
//      codecContext->me_sub_cmp = 2; // -subcmp 2
        codecContext->keyint_min = (int)round(framesPerSecond); 
//      codecContext->flags |= CODEC_FLAG_4MV; // 4mv
//      codecContext->flags |= CODEC_FLAG_LOOP_FILTER;
        codecContext->i_quant_factor = 0.71;
        codecContext->qcompress = 0.6;
//      codecContext->max_qdiff = 4;
        codecContext->flags2 |= CODEC_FLAG2_FASTPSKIP;

        if(outputFormat->flags & AVFMT_GLOBALHEADER)
        {
            codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
        }

        // Open the codec
        if (avcodec_open(codecContext, codec) < 0) 
        {
            NSLog(@"Couldn't initialize the codec");
            return;
        }

        // Open the file for recording
        if (avio_open(&outputFormatContext->pb, outputFormatContext->filename, AVIO_FLAG_WRITE) < 0) 
        { 
            NSLog(@"Couldn't open file");
            return;
        } 

        // Start by writing the video header
        if (avformat_write_header(outputFormatContext, NULL) < 0) 
        { 
            NSLog(@"Couldn't write video header");
            return;
        } 

        // Set up the video frame and output buffers
        outputBufferSize = 400000;
        outputBuffer = malloc(outputBufferSize);
        int size = codecContext->width * codecContext->height;

        int pictureBytes = avpicture_get_size(PIX_FMT_YUV420P, codecContext->width, codecContext->height);
        pictureBuffer = (uint8_t *)av_malloc(pictureBytes);

        videoFrame = avcodec_alloc_frame();
        videoFrame->data[0] = pictureBuffer;
        videoFrame->data[1] = videoFrame->data[0] + size;
        videoFrame->data[2] = videoFrame->data[1] + size / 4;
        videoFrame->linesize[0] = codecContext->width;
        videoFrame->linesize[1] = codecContext->width / 2;
        videoFrame->linesize[2] = codecContext->width / 2;

        avpicture_alloc(&inputRGBAFrame, PIX_FMT_BGRA, codecContext->width, codecContext->height);

        frameColorCounter = 0;

        movieStartTime = getNanoseconds();
    });
}

- (void)encodeNewFrameToMovie;
{
//  NSLog(@"Encode frame");

    if (dispatch_semaphore_wait(frameEncodingSemaphore, DISPATCH_TIME_NOW) != 0)
    {
        return;
    }

    dispatch_async(videoRecordingQueue, ^{
//      CFTimeInterval previousTimestamp = CFAbsoluteTimeGetCurrent();
        frameColorCounter++;

        if (codecContext == NULL)
        {       
            return;
        }

        // Take the input BGRA texture data and convert it to a YUV 4:2:0 planar frame
        avpicture_fill(&inputRGBAFrame, videoFrameToEncode, PIX_FMT_BGRA, codecContext->width, codecContext->height);
        struct SwsContext * img_convert_ctx = sws_getContext(codecContext->width, codecContext->height, PIX_FMT_BGRA, codecContext->width, codecContext->height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL); 
        sws_scale(img_convert_ctx, (const uint8_t* const *)inputRGBAFrame.data, inputRGBAFrame.linesize, 0, codecContext->height, videoFrame->data, videoFrame->linesize);

        // Encode the frame
        int out_size = avcodec_encode_video(codecContext, outputBuffer, outputBufferSize, videoFrame);  

        // Generate a packet and insert in the video stream
        if (out_size != 0) 
        {
            AVPacket videoPacket;
            av_init_packet(&videoPacket);

            if (codecContext->coded_frame->pts != AV_NOPTS_VALUE) 
            {
                uint64_t currentFrameTime = getNanoseconds();

                videoPacket.pts = av_rescale_q(((uint64_t)currentFrameTime - (uint64_t)movieStartTime) / 1000ull/*codecContext->coded_frame->pts*/, AV_TIME_BASE_Q/*codecContext->time_base*/, videoStream->time_base);

//              NSLog(@"Frame time %lld, converted time: %lld", ((uint64_t)currentFrameTime - (uint64_t)movieStartTime) / 1000ull, videoPacket.pts);
            }

            if(codecContext->coded_frame->key_frame)
            {
                videoPacket.flags |= AV_PKT_FLAG_KEY;
            }
            videoPacket.stream_index = videoStream->index;
            videoPacket.data = outputBuffer;
            videoPacket.size = out_size;

            int ret = av_write_frame(outputFormatContext, &videoPacket);
            if (ret < 0) 
            { 
                av_log(outputFormatContext, AV_LOG_ERROR, "%s","Error while writing frame.\n"); 
                av_free_packet(&videoPacket);
                return;
            } 

            av_free_packet(&videoPacket);
        }

//      CFTimeInterval frameDuration = CFAbsoluteTimeGetCurrent() - previousTimestamp;
//      NSLog(@"Frame duration: %f ms", frameDuration * 1000.0);

        dispatch_semaphore_signal(frameEncodingSemaphore);
    });
}

- (void)stopRecordingMovie;
{
    dispatch_async(videoRecordingQueue, ^{
        // Write out the video trailer
        if (av_write_trailer(outputFormatContext) < 0) 
        { 
            av_log(outputFormatContext, AV_LOG_ERROR, "%s","Error while writing trailer.\n"); 
            exit(1); 
        } 

        // Close out the file
        if (!(outputFormat->flags & AVFMT_NOFILE)) 
        {
            avio_close(outputFormatContext->pb);
        }

        // Free up all movie-related resources
        avcodec_close(codecContext);
        av_free(codecContext);
        codecContext = NULL;

        free(pictureBuffer);
        free(outputBuffer);

        av_free(videoFrame);
        av_free(outputFormatContext);
        av_free(videoStream);       
    });

}

#pragma mark -
#pragma mark Accessors

@synthesize framesPerSecond, videoFrameToEncode, movieFileName;

@end

Работает под Lion и Snow Leopard в 64-битном приложении. Он записывает с той же скоростью передачи данных, что и мой предыдущий подход, основанный на QuickTime, с общим меньшим использованием ЦП.

Надеюсь, это поможет кому-то еще в подобной ситуации.

5 голосов
/ 23 июля 2011

Я задал очень похожий вопрос инженеру QuickTime в прошлом месяце на WWDC, и они в основном предложили использовать 32-битный вспомогательный процесс ... Я знаю, что это не то, что вы хотели услышать. ;)

4 голосов
/ 07 августа 2011

Да, есть (по крайней мере) способ сделать не-QuickTime покадровую запись видео, которая более эффективна и производит файлы, сопоставимые с Quicktime.

Библиотека с открытым исходным кодом libavcodec идеально подходит для вашего случая кодирования видео. Он используется в очень популярных открытых и коммерческих программах и библиотеках (например, mplayer, google chrome, imagemagick, opencv). Он также предоставляет огромное количество опций для настройки и многочисленных форматов файлов (все важные форматы и множество экзотических форматов). ). Он эффективен и создает файлы со всеми видами скоростей.

Из Википедии:

libavcodec - свободная программа / библиотека с открытым исходным кодом, лицензированная LGPL. кодеки для кодирования и декодирования видео и аудио данных. [1] это предоставлено проектом FFmpeg или проектом Libav. [2] [3] libavcodec является неотъемлемая часть многих мультимедийных приложений с открытым исходным кодом и рамки. Популярные медиаплееры MPlayer, Xine и VLC используют его как их основной встроенный механизм декодирования, который позволяет воспроизводить многие аудио и видео форматы на всех поддерживаемых платформах. Он также используется пробный декодер ffdshow в качестве основной библиотеки декодирования. libavcodec также используется в приложениях для редактирования видео и транскодирования например, Avidemux, MEncoder или Kdenlive для декодирования и кодирования. libavcodec отличается тем, что содержит декодер, а иногда реализации кодировщиков нескольких проприетарных форматов, в том числе для которого не было выпущено ни одной публичной спецификации. Это обратное Таким образом, инженерные усилия являются важной частью libavcodec. развитие. Наличие таких кодеков в рамках стандарта Платформа libavcodec дает ряд преимуществ по сравнению с использованием оригинальные кодеки, в первую очередь повышенная переносимость, а в некоторых случаях также лучшая производительность, так как libavcodec содержит стандартную библиотеку высоко оптимизированных реализаций общих строительных блоков, таких как DCT и преобразование цветового пространства. Тем не менее, хотя libavcodec стремится к расшифровке, которая является точной для официальной реализации, ошибки и отсутствующие функции в таких переопределениях могут иногда проблемы совместимости при воспроизведении определенных файлов.

  • Вы можете импортировать FFmpeg напрямую в ваш проект XCode.
  • Другим решением является прямая передача кадров в FFmpeg. исполняемый файл.

Проект FFmpeg - это быстрый и точный мультимедийный транскодер, который может применять в различных сценариях на OS X.


FFmpeg (включая libavcodec) можно скомпилировать в mac
http://jungels.net/articles/ffmpeg-howto.html

FFmpeg (включая libavcodec) также может быть скомпилирован в 64 бит на снежном барсе
http://www.martinlos.com/?p=41

FFmpeg поддерживает огромное количество видео и аудио кодеков:
http://en.wikipedia.org/wiki/Libavcodec#Implemented_video_codecs

Обратите внимание, что libavcodec и FFmpeg - это LGPL, что означает, что вам нужно будет упомянуть, что вы их использовали, и вам не нужно открывать исходный код для вашего проекта.

...