Подтвердить что ты не робот

Что такое 10.6-совместимое средство записи видеокадров в фильм без использования QuickTime API?

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

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

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

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

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

4b9b3361

Ответ 1

В конце концов, я решил пойти с подходом, предложенным TiansHUo, и использовать libavcodec для сжатия видео здесь. Основываясь на инструкциях Martin здесь, я скачал источник 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 на сайте 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 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, при общем снижении использования ЦП.

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

Ответ 2

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

Ответ 3

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

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

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

libavcodec - бесплатная библиотека с лицензией LGPL с открытым исходным кодом кодеки для кодирования и декодирования видео и аудио данных. [1] это предоставленный проектом FFmpeg или проектом Libav. [2] [3] Что такое libavcodec? неотъемлемой частью многих мультимедийных приложений с открытым исходным кодом и рамки. Популярные медиаплееры MPlayer, xine и VLC используют его как их основной, встроенный механизм декодирования, который позволяет воспроизводить многие аудио и видео на всех поддерживаемых платформах. Он также используется ffdshow tryouts в качестве основной библиотеки декодирования. 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, что означает, что вы должны будете упомянуть, что вы их использовали, и вам не нужно открывать исходный код вашего проекта.