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\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就不用指定文件名了。
我在代码中尽量加了注释,下面是代码:
- /**
 - 他山之石,学习为主,版权所无,翻版不究,有错无责
 - 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, ...)
 - #endif
 - #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)
 - {
 - AVIOBufferContext* op = (AVIOBufferContext*)opaque;
 - if (op->pos + size > op->totalSize)
 - {
 - // 重新申请
 - // 根据数值逐步加大
 - 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)
 - {
 - AVIOBufferContext* op = (AVIOBufferContext*)opaque;
 - 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;
 - break;
 - case SEEK_END: // 此处可能有问题
 - new_pos = op->totalSize + offset;
 - break;
 - default:
 - return -1;
 - }
 - 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");
 - return -1;
 - }
 - 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");
 - ret = AVERROR_UNKNOWN;
 - goto end;
 - }
 - 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");
 - 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;
 - }
 - // 复制所有的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");
 - goto end;
 - }
 - 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");
 - goto end;
 - }
 - // 帧
 - while (1)
 - {
 - AVStream *in_stream, *out_stream;
 - //获取一个AVPacket
 - ret = av_read_frame(ifmt_ctx, &pkt);
 - if (ret < 0)
 - {
 - printf("av_read_frame failed or end of stream.\n");
 - break;
 - }
 - 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,
 - out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
 - 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");
 - break;
 - }
 - 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
FFMPEG基于内存的转码实例——输入输出视频均在内存的更多相关文章
- 最简单的基于FFmpeg的移动端样例:IOS 视频转码器
		
===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:A ...
 - 最简单的基于FFmpeg的移动端样例:Android 视频转码器
		
===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:A ...
 - FFmpeg编解码处理1-转码全流程简介
		
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584901.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...
 - Python 源码剖析(六)【内存管理机制】
		
六.内存管理机制 1.内存管理架构 2.小块空间的内存池 3.循环引用的垃圾收集 4.python中的垃圾收集 1.内存管理架构 Python内存管理机制有两套实现,由编译符号PYMALLOC_DEB ...
 - 源码分析:Java对象的内存分配
		
Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式.GC的实现方式.代的实现方式不同而具有 ...
 - FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分
		
===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...
 - WmS简介(三)之Activity窗口是如何创建的?基于Android7.0源码
		
OK,在前面两篇博客中我们分别介绍了WmS中的token,同时也向小伙伴们区分了Window和窗口的区别,并且按照type值的不同将Android系统中的窗口分为了三大类,那么本篇博客我们就来看看应用 ...
 - WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码
		
上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们 ...
 - jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行
		
动态类型语言 动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期. 举例子解释“类型检查”,例如代码: obj.println("hello world"); 假 ...
 
随机推荐
- VC++ 读写注冊表,注冊文件图标关联
			
#include <string> #include <iostream> #include <Windows.h> #include <shlobj.h&g ...
 - 白昼夢 / Daydream(模拟)
			
C - 白昼夢 / Daydream Time limit : 2sec / Memory limit : 256MB Score : 300 points Problem Statement You ...
 - 3.二级接口HierarchicalBeanFactory
			
HierarchicalBeanFactory 字面意思是分层工厂, 那么这个工厂是怎么分层的呢? package org.springframework.beans.factory; //分层工 ...
 - java 分布式锁  -图解- 秒懂
			
目录 写在前面 1.1. 分布式锁 简介 1.1.1. 图解:公平锁和可重入锁 模型 1.1.2. 图解: zookeeper分布式锁的原理 1.1.3. 分布式锁的基本流程 1.1.4. 加锁的实现 ...
 - 页游手游服务器(一)c实现拓展lua网络
			
把工作几年服务器相关的部分内容,通过服务器解决方案,做一次总结.整个实现的主体是lua脚本,lua实现主要缺少的两大块:1网络部分2数据库部分这两部分必须通过c/c++做扩展先来做net,主要是服务器 ...
 - delete 和 truncate 的 区别
			
如果要清空表中的所有记录,可以使用下面的两种方法: DELETE FROM table1 TRUNCATE TABLE table1 以下 为之区别: 1)执行速度和灵活性 trunca ...
 - js格式化货币金额
			
/* 格式化金额, s : 金额 n : 保留位数 */ function formatMoney(s, n) { n = n > 0 && n <= 20 ? n : 2 ...
 - java基础入门之九九乘法表
			
/* 自学java 九九乘法表 Power by Stuart Date: 2015.4.23 */public class Math { public static void main (Strin ...
 - Kattis - triangle 【数学】
			
题意 求第N个迭代三角形 中 所有黑色三角形的周长的整数部分的位数 思路 该三角形的周长是 3^(n + 1)/ 2 ^ (n) 然后 可以用 long double 存下来 再求位数 就可以 AC ...
 - 【LeetCode】 数相加组合 Combination Sum
			
描述 Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), ...