我在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就不用指定文件名了。

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

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

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

05-19 13:06