天天看點

FFMPEG基于記憶體的轉碼執行個體——輸入輸出視訊均在記憶體

技術要點簡述如下:

1、使用者自定義的操作

對于記憶體的操作使用結構體封裝:

typedef struct AVIOBufferContext {

    unsigned char* ptr;

    int pos;

    int totalSize;

    int realSize;

}AVIOBufferContext;

輸入、輸出均使用該結構體:

AVIOBufferContext g_avbuffer_in;

AVIOBufferContext g_avbuffer_out;

實作,read、write、seek函數。其中read為讀取時使用到的,其它2個是寫入記憶體要使用的。以read為例:

static int my_read(void *opaque, unsigned char *buf, int size)

{

    AVIOBufferContext* op = (AVIOBufferContext*)opaque;

    int len = size;

    if (op->pos + size > op->totalSize)

    {

        len = op->totalSize - op->pos;

    }

    memcpy(buf, op->ptr + op->pos, len);

    if (op->pos + len >= op->realSize)

    op->realSize += len;

    op->pos += len;

    return len;

}

實質進行的是讀取已有記憶體的size資料,拷貝到buf中。opaque友善參數傳遞。注意,在拷貝時要對pos累加。

其它函數類似。

2、輸出配置關鍵代碼:

    avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1,

                &g_avbuffer_out, NULL, my_write, my_seek);

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);

    if (!ofmt_ctx)

        printf( "Could not create output context\n");

        ret = AVERROR_UNKNOWN;

        goto end;

    ofmt_ctx->pb=avio_out; // 指派自定義的IO結構體

    ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

這個跟上述提到的文章是一緻的。隻是多了個自定義的結構體。

3、輸入配置關鍵代碼:

    avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0,

                &g_avbuffer_in, my_read, NULL, NULL); 

    if (!avio_in)

        printf( "avio_alloc_context for input failed\n");

    // 配置設定輸入的AVFormatContext

    ifmt_ctx=avformat_alloc_context();

    if (!ifmt_ctx)

    ifmt_ctx->pb=avio_in; // 指派自定義的IO結構體

    ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

    if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0)

        printf("Cannot open input file\n");

        return ret;

    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)

        printf("Cannot find stream information\n");

對于avio_alloc_context的指派和輸出一樣,隻是沒有了write和seek。對于輸入所用的AVFormatContext變量,用avformat_alloc_context來配置設定。由于是讀記憶體的資料,是以avformat_open_input就不用指定檔案名了。

我在代碼中盡量加了注釋,下面是代碼:

/** 

他山之石,學習為主,版權所無,翻版不究,有錯無責 

                  Late Lee  2015.08 

基于記憶體的格式封裝測試(從記憶體視訊轉換到另一片記憶體視訊) 

使用 

./a.out a.avi a.mkv 

支援的: 

avi mkv mp4 flv ts ... 

參考: 

http://blog.csdn.net/leixiaohua1020/article/details/25422685 

log 

新版本出現: 

Using AVStream.codec.time_base as a timebase hint to the muxer is  

deprecated. Set AVStream.time_base instead. 

test passed!! 

mp4->avi failed 

出現: 

H.264 bitstream malformed, no startcode found, use the h264_mp4toannexb bitstream filter  

解決見: 

http://blog.chinaunix.net/uid-11344913-id-4432752.html 

官方解釋: 

https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb 

ts -> avi passed 

其它: 

1、傳遞給ffmpeg的avio_alloc_context中的記憶體p和大小size,可以使用32768。 

如果轉換後的資料儲存在記憶體p1,這個記憶體p1一定要和前面所說的p不同。因為 

在自定義的write中的buf參數,就是p,是以要拷貝到其它記憶體。 

如定義p為32768,但定義p1為50MB,可以轉換50MB的視訊 

測試: 

p為32768時,需調用write 1351次 

2倍大小時,調用write 679次 

p越大,調用次數最少,記憶體消耗越大 

(用time測試,時間上沒什麼變化,前者為4.7s,後者為4.6s,但理論上記憶體大一點好) 

2、優化: 

   轉換功能接口封裝為類,把write、seek等和記憶體有關的操作放到類外部實作, 

   再傳遞到該類中,該類沒有記憶體管理更好一些。 

todo 

   重複讀檔案,如何做? 

*/  

#include <stdio.h>  

#include <stdlib.h>  

#include <unistd.h>  

#include <sys/types.h>  

#include <sys/stat.h>  

#include <fcntl.h>  

extern "C" {  

#include "libavcodec/avcodec.h"  

#include "libavformat/avformat.h"  

#include "libswscale/swscale.h"  

}  

#include "file_utils.h"  

#ifndef min  

#define min(a,b) ((a) > (b) ? (b) : (a))  

#endif  

#define _LL_DEBUG_  

// low level debug  

#ifdef _LL_DEBUG_  

    #define debug(fmt, ...) printf(fmt, ##__VA_ARGS__)  

    #define LL_DEBUG(fmt, ...) printf("[DEBUG %s().%d @ %s]: " fmt, \  

    __func__, __LINE__, P_SRC, ##__VA_ARGS__)  

#else  

    #define debug(fmt, ...)  

    #define LL_DEBUG(fmt, ...)  

#define DEFAULT_MEM (10*1024*1024)  

//參考file協定的記憶體,使用大小32768,大一點也可以  

#define IO_BUFFER_SIZE (32768*1)  

typedef struct AVIOBufferContext {  

    unsigned char* ptr;  

    int pos;  

    int totalSize;  

    int realSize;  

}AVIOBufferContext;  

// note 這兩個是使用者視訊資料,  

// g_avbuffer_in為已經讀取的視訊  

// g_avbuffer_out是ffmpeg轉換後的視訊,直接将該記憶體寫入檔案即可  

AVIOBufferContext g_avbuffer_in;  

AVIOBufferContext g_avbuffer_out;  

// note這兩個是FFMPEG内部使用的IO記憶體,與AVIOBufferContext的ptr不同  

// 在測試時,發現直接定義為數組,會有錯誤,故使用malloc  

static char *g_ptr_in = NULL;  

static char *g_ptr_out = NULL;  

// 每次read_frame時,就會調用到這個函數,該函數從g_avbuffer_in讀資料  

static int my_read(void *opaque, unsigned char *buf, int size)  

{  

    AVIOBufferContext* op = (AVIOBufferContext*)opaque;  

    int len = size;  

    if (op->pos + size > op->totalSize)  

    {  

        len = op->totalSize - op->pos;  

    }  

    memcpy(buf, op->ptr + op->pos, len);  

    if (op->pos + len >= op->realSize)  

    op->realSize += len;  

    op->pos += len;  

    return len;  

static int my_write(void *opaque, unsigned char *buf, int size)  

        // 重新申請  

        // 根據數值逐漸加大  

        int newTotalLen = op->totalSize*sizeof(char) * 3 / 2;  

        unsigned char* ptr = (unsigned char*)av_realloc(op->ptr, newTotalLen);  

        if (ptr == NULL)  

        {  

            // todo 是否在此處釋放記憶體?  

            return -1;  

        }  

        debug("org ptr: %p new ptr: %p size: %d(%0.fMB) ", op->ptr, ptr,   

                    newTotalLen, newTotalLen/1024.0/1024.0);  

        op->totalSize = newTotalLen;  

        op->ptr = ptr;  

        debug(" realloc!!!!!!!!!!!!!!!!!!!!!!!\n");  

    memcpy(op->ptr + op->pos, buf, size);  

    if (op->pos + size >= op->realSize)  

        op->realSize += size;  

    //static int cnt = 1;  

    //debug("%d write %p %p pos: %d len: %d\n", cnt++, op->ptr, buf, op->pos, size);  

    op->pos += size;  

    return 0;  

static int64_t my_seek(void *opaque, int64_t offset, int whence)  

    int64_t new_pos = 0; // 可以為負數  

    int64_t fake_pos = 0;  

    switch (whence)  

        case SEEK_SET:  

            new_pos = offset;  

            break;  

        case SEEK_CUR:  

            new_pos = op->pos + offset;  

        case SEEK_END: // 此處可能有問題  

            new_pos = op->totalSize + offset;  

        default:  

    fake_pos = min(new_pos, op->totalSize);  

    if (fake_pos != op->pos)  

        op->pos = fake_pos;  

    //debug("seek pos: %d(%d)\n", offset, op->pos);  

    return new_pos;  

int remuxer_mem_read(int argc, char* argv[])  

    //輸入對應一個AVFormatContext,輸出對應一個AVFormatContext  

    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;  

    AVIOContext *avio_in = NULL, *avio_out = NULL;  

    const char *in_filename = NULL, *out_filename = NULL;  

    AVPacket pkt;  

    int ret = 0;  

    if (argc < 3)  

        printf("usage: %s [input file] [output file]\n", argv[0]);  

        printf("eg %s foo.avi bar.ts\n", argv[0]);  

        return -1;  

    in_filename  = argv[1];  

    out_filename = argv[2];  

    memset(&g_avbuffer_in, '\0', sizeof(AVIOBufferContext));  

    memset(&g_avbuffer_out, '\0', sizeof(AVIOBufferContext));  

    read_file(in_filename, (char**)&g_avbuffer_in.ptr, &g_avbuffer_in.totalSize);  

    // 配置設定輸出視訊資料空間  

    g_avbuffer_out.ptr = (unsigned char*)av_realloc(NULL, DEFAULT_MEM*sizeof(char));  // new  

    if (g_avbuffer_out.ptr == NULL)  

        debug("alloc output mem failed.\n");  

    g_avbuffer_out.totalSize = DEFAULT_MEM;  

    memset(g_avbuffer_out.ptr, '\0', g_avbuffer_out.totalSize);  

    g_ptr_in = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));  

    g_ptr_out = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));  

    // 初始化  

    av_register_all();  

    // 輸出相關  

    // note 要指定IO記憶體,還在指定自定義的操作函數,這裡有write和seek  

    avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1,  

                &g_avbuffer_out, NULL, my_write, my_seek);   

    if (!avio_out)  

        printf( "avio_alloc_context failed\n");  

        ret = AVERROR_UNKNOWN;  

        goto end;  

    // 配置設定AVFormatContext  

    // 為友善起見,使用out_filename來根據輸出檔案擴充名來判斷格式  

    // 如果要使用如“avi”、“mp4”等指定,指派給第3個參數即可  

    // 注意該函數會配置設定AVOutputFormat  

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);  

    if (!ofmt_ctx)  

        printf( "Could not create output context\n");  

    ofmt_ctx->pb=avio_out; // 指派自定義的IO結構體  

    ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義  

    debug("guess format: %s(%s) flag: %d\n", ofmt_ctx->oformat->name,   

            ofmt_ctx->oformat->long_name, ofmt_ctx->oformat->flags);  

    //  輸入相關  

    // 配置設定自定義的AVIOContext 要差別于輸出的buffer  

    // 由于資料已經在記憶體中,是以指定read即可,不用write和seek  

    avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0,  

                &g_avbuffer_in, my_read, NULL, NULL);   

    if (!avio_in)  

        printf( "avio_alloc_context for input failed\n");  

    // 配置設定輸入的AVFormatContext  

    ifmt_ctx=avformat_alloc_context();  

    if (!ifmt_ctx)  

    ifmt_ctx->pb=avio_in; // 指派自定義的IO結構體  

    ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義  

    // 注:第二個參數本來是檔案名,但基于記憶體,不再有意義,随便用字元串  

    if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0)  

        printf("Cannot open input file\n");  

        return ret;  

    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)  

        printf("Cannot find stream information\n");  

    // 複制所有的stream  

    for (int i = 0; i < (int)(ifmt_ctx->nb_streams); i++)  

        //根據輸入流建立輸出流  

        AVStream *in_stream = ifmt_ctx->streams[i];  

        AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);  

        if (!out_stream)  

            printf( "Failed allocating output stream\n");  

            ret = AVERROR_UNKNOWN;  

            goto end;  

        //複制AVCodecContext的設定  

        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);  

        if (ret < 0)  

            printf( "Failed to copy context from input to output stream codec context\n");  

        out_stream->codec->codec_tag = 0;  

        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  

            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;  

    //輸出一下格式------------------  

    printf("output format:\n");  

    av_dump_format(ofmt_ctx, 0, out_filename, 1);  

    // 寫檔案頭  

    ret = avformat_write_header(ofmt_ctx, NULL);  

    if (ret < 0)  

        printf( "Error occurred when opening output file\n");  

    // 幀  

    while (1)  

        AVStream *in_stream, *out_stream;  

        //擷取一個AVPacket  

        ret = av_read_frame(ifmt_ctx, &pkt);  

            printf("av_read_frame failed or end of stream.\n");  

        in_stream  = ifmt_ctx->streams[pkt.stream_index];  

        out_stream = ofmt_ctx->streams[pkt.stream_index];  

        //轉換PTS/DTS  

        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,   

            out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  

        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base,  

        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);  

        pkt.pos = -1;  

        // 寫入一幀  

        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);  

        if (ret < 0) {  

            printf( "Error muxing packet\n");  

        av_free_packet(&pkt);  

    //寫檔案尾(Write file trailer)  

    printf("--------write trailer------------\n");  

    av_write_trailer(ofmt_ctx);  

    // 把輸出的視訊寫到檔案中  

    printf("write to file: %s %p %d\n", out_filename, g_avbuffer_out.ptr, g_avbuffer_out.realSize);  

    write_file(out_filename, (char*)g_avbuffer_out.ptr, g_avbuffer_out.realSize, 1);  

end:  

    if (avio_in != NULL)  av_freep(avio_in);   

    if (avio_out != NULL) av_freep(avio_out);  

    //if (g_ptr_in != NULL) free(g_ptr_in);  

    if (g_ptr_out != NULL) free(g_ptr_out);  

    // 該函數會釋放使用者自定義的IO buffer,上面不再釋放,否則會corrupted double-linked list  

    avformat_close_input(&ifmt_ctx);  

    avformat_free_context(ofmt_ctx);  

    if (g_avbuffer_in.ptr != NULL) free(g_avbuffer_in.ptr);  

    if (g_avbuffer_out.ptr != NULL) free(g_avbuffer_out.ptr);  

    return ret;  

from:http://blog.csdn.net/subfate/article/details/48001433

繼續閱讀