我在6月份写了篇文章​​《FFMPEG基于内存的转码实例》​​,讲如何把视频转码后放到内存,然后通过网络发送出去。但该文章只完成了一半,即输入的数据依然是从磁盘文件中读取。在实际应用中,有很多数据是放到内存的,比如播放从服务器接收到的视频,就是在内存中的。时隔2个月,项目终于完成了,虽然在收尾阶段会花费大量时间,但也算空闲了点。于是就继续完善。

本文中,假定的使用场合是,有一个已经放到内存的视频,需要将它转码成另一种封装格式,还是放到内存中。由于是​​测试​​,首先将视频从文件中读取到内存,最后会将转换好的视频写入到另一个文件以检查是否正常。当然,限于能力,代码不可能适合于所有情况,但却可以清楚演示出自定义的IO输入输出的用法。

技术要点简述如下:

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");

        ret = AVERROR_UNKNOWN;

        goto end;

    }

    // 分配输入的AVFormatContext

    ifmt_ctx=avformat_alloc_context();

    if (!ifmt_ctx)

    {

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

        ret = AVERROR_UNKNOWN;

        goto end;

    }

    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");

        return ret;

    }

对于avio_alloc_context的赋值和输出一样,只是没有了write和seek。对于输入所用的AVFormatContext变量,用avformat_alloc_context来分配。由于是读内存的数据,因此avformat_open_input就不用指定文件名了。

我在代码中尽量加了注释,下面是代码:




[cpp] ​​view plain​​ ​​copy​

 


  1. /** 
  2. 他山之石,学习为主,版权所无,翻版不究,有错无责 
  3.                   Late Lee  2015.08 
  4. 基于内存的格式封装测试(从内存视频转换到另一片内存视频) 
  5. 使用 
  6. ./a.out a.avi a.mkv 
  7.  
  8. 支持的: 
  9. avi mkv mp4 flv ts ... 
  10.  
  11. 参考: 


  1.  
  2. log 
  3. 新版本出现: 
  4. Using AVStream.codec.time_base as a timebase hint to the muxer is  
  5. deprecated. Set AVStream.time_base instead. 
  6.  
  7. test passed!! 
  8.  
  9. mp4->avi failed 
  10. 出现: 
  11. H.264 bitstream malformed, no startcode found, use the h264_mp4toannexb bitstream filter  
  12. 解决见: 
  13. http://blog.chinaunix.net/uid-11344913-id-4432752.html 
  14. 官方解释: 
  15. https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb 
  16.  
  17.  
  18. ts -> avi passed 
  19.  
  20. 其它: 
  21.  
  22. 1、传递给ffmpeg的avio_alloc_context中的内存p和大小size,可以使用32768。 
  23. 如果转换后的数据保存在内存p1,这个内存p1一定要和前面所说的p不同。因为 
  24. 在自定义的write中的buf参数,就是p,所以要拷贝到其它内存。 
  25. 如定义p为32768,但定义p1为50MB,可以转换50MB的视频 
  26. 测试: 
  27. p为32768时,需调用write 1351次 
  28. 2倍大小时,调用write 679次 
  29. p越大,调用次数最少,内存消耗越大 
  30. (用time测试,时间上没什么变化,前者为4.7s,后者为4.6s,但理论上内存大一点好) 
  31.  
  32. 2、优化: 
  33.    转换功能接口封装为类,把write、seek等和内存有关的操作放到类外部实现, 
  34.    再传递到该类中,该类没有内存管理更好一些。 
  35.  
  36. todo 
  37.    重复读文件,如何做? 
  38. */  
  39.   
  40. #include <stdio.h>  
  41. #include <stdlib.h>  
  42. #include <unistd.h>  
  43.   
  44. #include <sys/types.h>  
  45. #include <sys/stat.h>  
  46. #include <fcntl.h>  
  47.   
  48. extern "C" {  
  49. #include "libavcodec/avcodec.h"  
  50. #include "libavformat/avformat.h"  
  51. #include "libswscale/swscale.h"  
  52. }  
  53.   
  54. #include "file_utils.h"  
  55.   
  56. #ifndef min  
  57. #define min(a,b) ((a) > (b) ? (b) : (a))  
  58. #endif  
  59.   
  60. #define _LL_DEBUG_  
  61.   
  62. // low level debug  
  63. #ifdef _LL_DEBUG_  
  64.     #define debug(fmt, ...) printf(fmt, ##__VA_ARGS__)  
  65.     #define LL_DEBUG(fmt, ...) printf("[DEBUG %s().%d @ %s]: " fmt, \  
  66.     __func__, __LINE__, P_SRC, ##__VA_ARGS__)  
  67. #else  
  68.     #define debug(fmt, ...)  
  69.     #define LL_DEBUG(fmt, ...)  
  70. #endif  
  71.   
  72. #define DEFAULT_MEM (10*1024*1024)  
  73.   
  74. //参考file协议的内存,使用大小32768,大一点也可以  
  75. #define IO_BUFFER_SIZE (32768*1)  
  76.   
  77. typedef struct AVIOBufferContext {  
  78.     unsigned char* ptr;  
  79.     int pos;  
  80.     int totalSize;  
  81.     int realSize;  
  82. }AVIOBufferContext;  
  83.   
  84. // note 这两个是用户视频数据,  
  85. // g_avbuffer_in为已经读取的视频  
  86. // g_avbuffer_out是ffmpeg转换后的视频,直接将该内存写入文件即可  
  87. AVIOBufferContext g_avbuffer_in;  
  88. AVIOBufferContext g_avbuffer_out;  
  89.   
  90. // note这两个是FFMPEG内部使用的IO内存,与AVIOBufferContext的ptr不同  
  91. // 在测试时,发现直接定义为数组,会有错误,故使用malloc  
  92. static char *g_ptr_in = NULL;  
  93. static char *g_ptr_out = NULL;  
  94.   
  95. // 每次read_frame时,就会调用到这个函数,该函数从g_avbuffer_in读数据  
  96. static int my_read(void *opaque, unsigned char *buf, int size)  
  97. {  
  98.     AVIOBufferContext* op = (AVIOBufferContext*)opaque;  
  99.     int len = size;  
  100.     if (op->pos + size > op->totalSize)  
  101.     {  
  102.         len = op->totalSize - op->pos;  
  103.     }  
  104.     memcpy(buf, op->ptr + op->pos, len);  
  105.     if (op->pos + len >= op->realSize)  
  106.     op->realSize += len;  
  107.       
  108.     op->pos += len;  
  109.   
  110.     return len;  
  111. }  
  112.   
  113. static int my_write(void *opaque, unsigned char *buf, int size)  
  114. {  
  115.     AVIOBufferContext* op = (AVIOBufferContext*)opaque;  
  116.     if (op->pos + size > op->totalSize)  
  117.     {  
  118.         // 重新申请  
  119.         // 根据数值逐步加大  
  120.         int newTotalLen = op->totalSize*sizeof(char) * 3 / 2;  
  121.         unsigned char* ptr = (unsigned char*)av_realloc(op->ptr, newTotalLen);  
  122.         if (ptr == NULL)  
  123.         {  
  124.             // todo 是否在此处释放内存?  
  125.             return -1;  
  126.         }  
  127.         debug("org ptr: %p new ptr: %p size: %d(%0.fMB) ", op->ptr, ptr,   
  128.                     newTotalLen, newTotalLen/1024.0/1024.0);  
  129.         op->totalSize = newTotalLen;  
  130.         op->ptr = ptr;  
  131.         debug(" realloc!!!!!!!!!!!!!!!!!!!!!!!\n");  
  132.     }  
  133.     memcpy(op->ptr + op->pos, buf, size);  
  134.   
  135.     if (op->pos + size >= op->realSize)  
  136.         op->realSize += size;  
  137.   
  138.     //static int cnt = 1;  
  139.     //debug("%d write %p %p pos: %d len: %d\n", cnt++, op->ptr, buf, op->pos, size);  
  140.       
  141.     op->pos += size;  
  142.   
  143.     return 0;  
  144. }  
  145.   
  146. static int64_t my_seek(void *opaque, int64_t offset, int whence)  
  147. {  
  148.     AVIOBufferContext* op = (AVIOBufferContext*)opaque;  
  149.     int64_t new_pos = 0; // 可以为负数  
  150.     int64_t fake_pos = 0;  
  151.   
  152.     switch (whence)  
  153.     {  
  154.         case SEEK_SET:  
  155.             new_pos = offset;  
  156.             break;  
  157.         case SEEK_CUR:  
  158.             new_pos = op->pos + offset;  
  159.             break;  
  160.         case SEEK_END: // 此处可能有问题  
  161.             new_pos = op->totalSize + offset;  
  162.             break;  
  163.         default:  
  164.             return -1;  
  165.     }  
  166.       
  167.     fake_pos = min(new_pos, op->totalSize);  
  168.     if (fake_pos != op->pos)  
  169.     {  
  170.         op->pos = fake_pos;  
  171.     }  
  172.     //debug("seek pos: %d(%d)\n", offset, op->pos);  
  173.     return new_pos;  
  174. }  
  175.   
  176. int remuxer_mem_read(int argc, char* argv[])  
  177. {  
  178.     //输入对应一个AVFormatContext,输出对应一个AVFormatContext  
  179.     AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;  
  180.     AVIOContext *avio_in = NULL, *avio_out = NULL;  
  181.     const char *in_filename = NULL, *out_filename = NULL;  
  182.     AVPacket pkt;  
  183.       
  184.     int ret = 0;  
  185.   
  186.     if (argc < 3)  
  187.     {  
  188.         printf("usage: %s [input file] [output file]\n", argv[0]);  
  189.         printf("eg %s foo.avi bar.ts\n", argv[0]);  
  190.         return -1;  
  191.     }  
  192.   
  193.     in_filename  = argv[1];  
  194.     out_filename = argv[2];  
  195.   
  196.     memset(&g_avbuffer_in, '\0', sizeof(AVIOBufferContext));  
  197.     memset(&g_avbuffer_out, '\0', sizeof(AVIOBufferContext));  
  198.   
  199.     read_file(in_filename, (char**)&g_avbuffer_in.ptr, &g_avbuffer_in.totalSize);  
  200.       
  201.     // 分配输出视频数据空间  
  202.     g_avbuffer_out.ptr = (unsigned char*)av_realloc(NULL, DEFAULT_MEM*sizeof(char));  // new  
  203.     if (g_avbuffer_out.ptr == NULL)  
  204.     {  
  205.         debug("alloc output mem failed.\n");  
  206.         return -1;  
  207.     }  
  208.     g_avbuffer_out.totalSize = DEFAULT_MEM;  
  209.     memset(g_avbuffer_out.ptr, '\0', g_avbuffer_out.totalSize);  
  210.       
  211.     g_ptr_in = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));  
  212.     g_ptr_out = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));  
  213.   
  214.     // 初始化  
  215.     av_register_all();  
  216.   
  217.     // 输出相关  
  218.     // note 要指定IO内存,还在指定自定义的操作函数,这里有write和seek  
  219.     avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1,  
  220.                 &g_avbuffer_out, NULL, my_write, my_seek);   
  221.     if (!avio_out)  
  222.     {  
  223.         printf( "avio_alloc_context failed\n");  
  224.         ret = AVERROR_UNKNOWN;  
  225.         goto end;  
  226.     }  
  227.     // 分配AVFormatContext  
  228.     // 为方便起见,使用out_filename来根据输出文件扩展名来判断格式  
  229.     // 如果要使用如“avi”、“mp4”等指定,赋值给第3个参数即可  
  230.     // 注意该函数会分配AVOutputFormat  
  231.     avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);  
  232.     if (!ofmt_ctx)  
  233.     {  
  234.         printf( "Could not create output context\n");  
  235.         ret = AVERROR_UNKNOWN;  
  236.         goto end;  
  237.     }  
  238.     ofmt_ctx->pb=avio_out; // 赋值自定义的IO结构体  
  239.     ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定为自定义  
  240.   
  241.     debug("guess format: %s(%s) flag: %d\n", ofmt_ctx->oformat->name,   
  242.             ofmt_ctx->oformat->long_name, ofmt_ctx->oformat->flags);  
  243.   
  244.   
  245.     //  输入相关  
  246.     // 分配自定义的AVIOContext 要区别于输出的buffer  
  247.     // 由于数据已经在内存中,所以指定read即可,不用write和seek  
  248.     avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0,  
  249.                 &g_avbuffer_in, my_read, NULL, NULL);   
  250.     if (!avio_in)  
  251.     {  
  252.         printf( "avio_alloc_context for input failed\n");  
  253.         ret = AVERROR_UNKNOWN;  
  254.         goto end;  
  255.     }  
  256.     // 分配输入的AVFormatContext  
  257.     ifmt_ctx=avformat_alloc_context();  
  258.     if (!ifmt_ctx)  
  259.     {  
  260.         printf( "Could not create output context\n");  
  261.         ret = AVERROR_UNKNOWN;  
  262.         goto end;  
  263.     }  
  264.     ifmt_ctx->pb=avio_in; // 赋值自定义的IO结构体  
  265.     ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定为自定义  
  266.   
  267.     // 注:第二个参数本来是文件名,但基于内存,不再有意义,随便用字符串  
  268.     if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0)  
  269.     {  
  270.         printf("Cannot open input file\n");  
  271.         return ret;  
  272.     }  
  273.     if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)  
  274.     {  
  275.         printf("Cannot find stream information\n");  
  276.         return ret;  
  277.     }  
  278.   
  279.     // 复制所有的stream  
  280.     for (int i = 0; i < (int)(ifmt_ctx->nb_streams); i++)  
  281.     {  
  282.         //根据输入流创建输出流  
  283.         AVStream *in_stream = ifmt_ctx->streams[i];  
  284.         AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);  
  285.         if (!out_stream)  
  286.         {  
  287.             printf( "Failed allocating output stream\n");  
  288.             ret = AVERROR_UNKNOWN;  
  289.             goto end;  
  290.         }  
  291.         //复制AVCodecContext的设置  
  292.         ret = avcodec_copy_context(out_stream->codec, in_stream->codec);  
  293.         if (ret < 0)  
  294.         {  
  295.             printf( "Failed to copy context from input to output stream codec context\n");  
  296.             goto end;  
  297.         }  
  298.         out_stream->codec->codec_tag = 0;  
  299.         if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  
  300.             out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;  
  301.     }  
  302.     //输出一下格式------------------  
  303.     printf("output format:\n");  
  304.     av_dump_format(ofmt_ctx, 0, out_filename, 1);  
  305.   
  306.     // 写文件头  
  307.     ret = avformat_write_header(ofmt_ctx, NULL);  
  308.     if (ret < 0)  
  309.     {  
  310.         printf( "Error occurred when opening output file\n");  
  311.         goto end;  
  312.     }  
  313.   
  314.     // 帧  
  315.     while (1)  
  316.     {  
  317.         AVStream *in_stream, *out_stream;  
  318.         //获取一个AVPacket  
  319.         ret = av_read_frame(ifmt_ctx, &pkt);  
  320.         if (ret < 0)  
  321.         {  
  322.             printf("av_read_frame failed or end of stream.\n");  
  323.             break;  
  324.         }  
  325.         in_stream  = ifmt_ctx->streams[pkt.stream_index];  
  326.         out_stream = ofmt_ctx->streams[pkt.stream_index];  
  327.   
  328.         //转换PTS/DTS  
  329.         pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,   
  330.             out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  331.         pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base,  
  332.             out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
  333.         pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);  
  334.         pkt.pos = -1;  
  335.   
  336.         // 写入一帧  
  337.         ret = av_interleaved_write_frame(ofmt_ctx, &pkt);  
  338.         if (ret < 0) {  
  339.             printf( "Error muxing packet\n");  
  340.             break;  
  341.         }  
  342.         av_free_packet(&pkt);  
  343.     }  
  344.   
  345.     //写文件尾(Write file trailer)  
  346.     printf("--------write trailer------------\n");  
  347.     av_write_trailer(ofmt_ctx);  
  348.   
  349.     // 把输出的视频写到文件中  
  350.     printf("write to file: %s %p %d\n", out_filename, g_avbuffer_out.ptr, g_avbuffer_out.realSize);  
  351.     write_file(out_filename, (char*)g_avbuffer_out.ptr, g_avbuffer_out.realSize, 1);  
  352.   
  353. end:  
  354.     if (avio_in != NULL)  av_freep(avio_in);   
  355.     if (avio_out != NULL) av_freep(avio_out);  
  356.     //if (g_ptr_in != NULL) free(g_ptr_in);  
  357.     if (g_ptr_out != NULL) free(g_ptr_out);  
  358.   
  359.     // 该函数会释放用户自定义的IO buffer,上面不再释放,否则会corrupted double-linked list  
  360.     avformat_close_input(&ifmt_ctx);  
  361.     avformat_free_context(ofmt_ctx);  
  362.   
  363.     if (g_avbuffer_in.ptr != NULL) free(g_avbuffer_in.ptr);  
  364.     if (g_avbuffer_out.ptr != NULL) free(g_avbuffer_out.ptr);  
  365.   
  366.     return ret;  
  367. }