我在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/25422685log
新版本出现:
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_005fmp4toannexbts -> 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,输出对应一个AVFormatContextAVFormatContext *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));  // newif (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和seekavio_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个参数即可// 注意该函数会分配AVOutputFormatavformat_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和seekavio_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;}// 分配输入的AVFormatContextifmt_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;}// 复制所有的streamfor (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;//获取一个AVPacketret = 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/DTSpkt.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 listavformat_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;
}

PS:有人问我为什么在代码里经常看到LL_DEBUG,实际上“LL”是本人大名。至于解释为“low level”,那是骗人的。

李迟 2015.8.26 周三 晚,吃项目饭



FFMPEG基于内存的转码实例——输入输出视频均在内存相关推荐

  1. java和ffmpeg使用内存转码_FFMPEG基于内存的转码实例

    我在6月份写了篇文章<FFMPEG基于内存的转码实例>,讲如何把视频转码后放到内存,然后通过网络发送出去.但该文章只完成了一半,即输入的数据依然是从磁盘文件中读取.在实际应用中,有很多数据 ...

  2. FFMPEG基于内存的转码实例

    前面有文章写道,尝试在FFMPEG里面直接使用FTP协议将H.264祼码流封装为AVI视频格式存储到FTP服务器上,遗憾的是以失败告终.然此事须解决,在多方考虑之后,决定使用内存临时存储,而不是写入磁 ...

  3. c语言复制粘贴源码,c语言函数memccpy()如何复制内存中的内容实例源码介绍

    c语言函数memccpy()如何复制内存中的内容实例源码介绍.引入的头文件:#include memccpy()函数定义:void * memccpy(void *dest, const void * ...

  4. 最简单的基于FFmpeg的移动端样例:IOS 视频转码器

    ===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:A ...

  5. 【深入Java虚拟机JVM 04】JVM内存溢出OutOfMemoryError异常实例

    说明:文章所有内容均摘自<深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)> 在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMe ...

  6. JVM运行时结构、Java内存管理、JVM实例、HotSpot VM对象的创建、内存布局和访问定位

    1.JVM运行时结构 Java 运行时数据区域有程序计数器.Java虚拟机栈.本地方法栈.Java堆和方法区.其中前三个线程私有,随线程生而生,线程灭而灭:后面两个是线程间共享. 1.1 程序计数器 ...

  7. DPDK 跟踪库tracepoint源码实例分析

    DPDK笔记 DPDK 跟踪库tracepoint源码实例分析 RToax 2021年4月 注意: 跟踪库 基于DPDK 20.05 DPDK跟踪库:trace library 1. trace流程源 ...

  8. Android 匿名共享内存驱动源码分析

    原址 Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现,本文通过源码分析方式详细介绍Android系统的匿名共享内存机制.在Android系统中,匿 ...

  9. Flink内存管理源码解读之基础数据结构

    概述 在分布式实时计算领域,如何让框架/引擎足够高效地在内存中存取.处理海量数据是一个非常棘手的问题.在应对这一问题上Flink无疑是做得非常杰出的,Flink的自主内存管理设计也许比它自身的知名度更 ...

最新文章

  1. Spring boot修改静态资源映射
  2. [导入]毕业的大学生的100条忠告
  3. GitHub 上 57 款最流行的开源深度学习项目【转】
  4. [Java基础]JDK内置注解
  5. python实现qq登录界面_使用Python编写一个QQ办公版的图形登录界面!
  6. redis的info
  7. python基本执行方式_Python开发的3种命令执行方法
  8. 学小易电脑端——大学生搜题平台
  9. 查看自己电脑连接过的WiFi密码
  10. 3.19美团实习面试一面二面(已offer)
  11. 征服游戏 Floyd算法
  12. seo优化notfallow有二种下拉菜单百度搜索引擎
  13. 群晖经典第三方套件_使用群晖第三方套件进行IPV6的DDNS
  14. 腾讯云搭建vsftpd服务器
  15. debug : 应用程序无法正常启动(0xc000007b)
  16. matlab 图形绘制,MatLab图形绘制功能
  17. 微信小程序实现文字随机颜色
  18. 企业采购管理系统SRM
  19. 怎么区分开关电源的PFM与PWM模式?
  20. 使用devops的团队_DevOps团队如何为网络星期一做准备

热门文章

  1. python 文件函数_python文件操作及函数学习
  2. iPhone 14系列影像规格曝光:长焦镜头或再度缺席
  3. 不会有1TB?研究机构称iPhone 13最高存储仍为512GB
  4. 蔚来:首台ET7白车身合肥工厂下线
  5. 年会尽头是闲鱼!超11万人在闲鱼转卖年会奖品
  6. 价格高达万元!苹果将推1TB存储版本的iPhone
  7. 开到朝鲜的国产十元店,一年如何卖出190亿?
  8. 优酷直播节目“冠军体育课”第二季收官 共吸引近百万用户观看互动
  9. 跨过山和大海的地铁外放党们有人管了,明年开始!网友:没收手机么?
  10. 已然是身份的象征了?Coach品牌再入天猫 只有目标用户才有机会看到