zoukankan      html  css  js  c++  java
  • FFMPEG基于内存的转码实例——输入输出视频均在内存

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

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

  • 相关阅读:
    IntelliJ IDEA Java编译打包
    java 播放mp3文件
    cool edit录音声卡
    VB调用VB脚本VBS向Http请求的三种方式
    asp.net 各种下载方式汇总
    Action拦截器接口-- IActionFilter,IExceptionFilter, IResultFilter
    Centos 常用命令
    医院信息集成平台ESB技术框架
    【划重点】医疗软件行业关键概念扫盲
    互联网医院安全架构
  • 原文地址:https://www.cnblogs.com/lidabo/p/7346377.html
Copyright © 2011-2022 走看看