ffmpeg 解封装到内存

A- A+

FFMPEG基于内存的转码实例

2015-06-09 13:20 开源项目, 我的研究, 流媒体学习 评论 1 条 阅读 5,719 次
<div class="single-main">
	<div class="content-main">
		<p>前面有文章写道,尝试在FFMPEG里面直接使用FTP协议将H.264祼码流封装为AVI视频格式存储到FTP服务器上,遗憾的是以失败告终。然此事须解决,在多方考虑之后,决定使用内存临时存储,而不是写入磁盘文件,一来不必要,二来对磁盘有损耗。</p>

FFMPEG支持内存转码的,但没有现成可用的封装得很好的接口。几经翻阅网络文章,找到了些许方法,结合自己的实验,完成既定目标。网络上暂未发现有此类应用,写出来,尽一人绵薄之力。至于经常偷窃他人成果,亦于此略表心意。

在FFMPEG中,转换视频有一套固定的方式,初始化好参数后,就是调用avformat_write_header写头部信息,调用av_write_frame写一帧数据,调用av_write_trailer写尾部信息,它们都使用AVFormatContext结构体作为参数。

对于使用文件名(file协议)的情况,初始化参数后,调用avio_open来打开文件。后面可以不用管文件名称了。
对于使用ftp协议的情况,除了在协议路径上加“ftp://ip/”,其它的步骤和上述情况一样。但必须注意的是,编译FFMPEG时要添加对应的协议,否则会不成功。另外还要在代码加上avformat_network_init函数进行初始化。

说回本文重点。本文关注的是使用FFMPEG将转换好的视频数据放到内存中,该内存中的数据是完全的视频,直接保存成文件即可播放而不用。很多年以前搞AVI时研究过其格式,知道在写AVI文件时要将文件指针定位到文件头部,写文件总大小,会使用seek函数。有的FTP服务是不支持回写和sekk功能的,这也是为什么我之前的尝试会失败。

鉴于此,想到了将所有的视频数据放到开辟好的内存,而seek则直接用指针来运算。网络有这方面的文章,请参考文后地址。
FFMPEG支持自定义AVIOContext,使用avio_alloc_context返回AVIOContext结构体,同时该函数会传递自定义的read、write、seek函数。再使用avformat_alloc_output_context2函数分配AVFormatContext结构体,再将上面分配的AVIOContext结构体赋值给AVFormatContext的pb成员。

下面说说我所做的步骤。

1、定义好write、seek或read。具体参考文后源码。要注意的是,这里没有读操作,因此不实现,关注的是转码后的数据,因而实现了write和seek。在write过程中,会出现内存不够的情况所以我使用av_realloc函数来扩展内存,每次只扩展一半,不够再扩。

2、创建AVIOContext,关赋值,示例如下:

  
  
AVFormatContext *ofmt_ctx = NULL; AVIOContext *avio_out = NULL; avio_out =avio_alloc_context((unsigned char *)g_ptr1, IO_BUFFER_SIZE, 1, NULL, NULL, my_write, my_seek); avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename); ofmt_ctx->pb=avio_out; // 赋值自定义的IO结构体 ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定为自定义

在这里要注意avformat_alloc_output_context2函数,它会分配AVOutputFormat(如果不指定的话),out_filename是根据文件名来判断是哪一种格式,因为我没有限制使用什么格式,这和前面提到不使用文件是两回事。详见代码。
3、由于不使用文件了,所以不需要调用avio_open了。我在这里也纠结好久,一时没转过弯来。

其它就和普通文件的操作类似。

示例代码:

  
  
/** 他山之石,学习为主,版权所无,翻版不究,有错无责

基于内存的格式封装测试
使用
./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、自定USE_MEM,则转换后的数据在内存中,要用write将其写回到文件
   如果不定义,则每一次write都会写到文件,seek也是文件操作

2、传递给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)

3、优化:
   转换功能接口封装为类,把write、seek等和内存有关的操作放到类外部实现,
   再传递到该类中,该类没有内存管理更好一些。
*/

#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”
}

#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, 
    funcLINE, P_SRC, ##VA_ARGS)
#else
    #define debug(fmt, …)
    #define LL_DEBUG(fmt, …)
#endif

// 自定义的IO全部在内存
#define USE_MEM

#define DEFAULT_MEM (1010241024)

#define IO_BUFFER_SIZE (32768*1)

static char g_ptr1[IO_BUFFER_SIZE] = {0};

static char* g_ptr = NULL;
//static char g_ptr[DEFAULT_MEM] = {0};
static int g_nPos = 0;
static int g_nTotalLen = DEFAULT_MEM;

static int g_nRealLen = 0;

static int g_fd = 0;

int my_read(void *opaque, unsigned char *buf, int buf_size)
{
    // no
    return 0;
}

int my_write(void opaque, unsigned char buf, int size)
{
    if (g_nPos + size > g_nTotalLen)
    {
        // 重新申请
        // 根据数值逐步加大
        int nTotalLen = g_nTotalLen
sizeof(char
 3 / 2;
        char ptr = (char)av_realloc(g_ptr, nTotalLen);
        if (ptr == NULL)
        {
// if (g_ptr) av_free(g_ptr);
            return -1;
        }
        debug(“org ptr: %p new ptr: %p size: %d(%0.fMB) “, g_ptr, ptr, 
                    nTotalLen, nTotalLen/1024.0/1024.0);
        g_nTotalLen = nTotalLen;
        g_ptr = ptr;
        debug(” realloc!!!\n”);
        // todo
        //memcpy(g_ptr + g_nPos, buf, size);
    }
    memcpy(g_ptr + g_nPos, buf, size);

    if (g_nPos + size >= g_nRealLen)
        g_nRealLen += size;
    
    static int cnt = 1;
    debug("%d write %p %p pos: %d len: %d\n", cnt++, g_ptr, buf, g_nPos, size);
    //dump("org ", (char*)buf, 32);
    //dump("g_ptr ", g_ptr, 32);
    
    g_nPos += size;
    return 0;
}

int64_t my_seek(void *opaque, int64_t offset, int whence)
{
    int64_t new_pos = 0// 可以为负数
    int64_t fake_pos = 0;

    switch (whence)
    {
        case SEEK_SET:
            new_pos = offset;
            break;
        case SEEK_CUR:
            new_pos = g_nPos + offset;
            break;
        case SEEK_END: // 此处可能有问题
            new_pos = g_nTotalLen + offset;
            break;
        default:
            return -1;
    }
    
    fake_pos = min(new_pos, g_nTotalLen);
    if (fake_pos != g_nPos)
    {
        g_nPos = fake_pos;
    }
    debug(“seek pos: %d(%d)\n”, offset, g_nPos);
    return new_pos;
}

int my_write1(void opaque, unsigned char buf, int size)
{
    debug(“write %p len: %d\n”,  buf, size);
    //dump("org ", (char
)buf, 32);
    //dump("g_ptr ", (char
)g_ptr, 32);
    return write(g_fd, buf, size);
}

int64_t my_seek1(void *opaque, int64_t offset, int whence)
{
    debug(“seek pos: %d whence: %d\n”, offset, whence);
    return lseek(g_fd, offset, whence);
}

// 定义2个函数指针,免得后面改
static int (*write_packet)(void *opaque, uint8_t *buf, int buf_size) = 
#ifdef USE_MEM
my_write
#else
my_write1
#endif
;

static int64_t (*seek)(void *opaque, int64_t offset, int whence) =
#ifdef USE_MEM
my_seek
#else
my_seek1
#endif
;

int remuxer_mem(int argc, char* argv[])
{
    //输入对应一个AVFormatContext,输出对应一个AVFormatContext
    //(Input AVFormatContext and Output AVFormatContext)
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename;
    int ret = 0;
    AVIOContext *avio_out = NULL; 

    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];

    // 分配空间
    g_ptr = (char)av_realloc(NULL, DEFAULT_MEMsizeof(char));  // new
    if (g_ptr == NULL)
    {
        debug(“alloc mem failed.\n”);
        return -1;
    }
    printf("------alloc ptr: %p\n", g_ptr);
    memset(g_ptr, ‘\0’, DEFAULT_MEM);

    av_register_all();

    //输入(Input)
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 00)) < 0)
    {
        printf( “Could not open input file ‘%s’: %s\n”, in_filename, strerror(errno));
        goto end;
    }
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)
    {
        printf( “Failed to retrieve input stream information\n”);
        goto end;
    }

    printf(“input format:\n”);
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    //输出(Output)
    
    // 先打开要保存的文件
    g_fd = open(out_filename, O_CREAT|O_RDWR, 0666);
    
    // 分配自定义的AVIOContext
    // 注意,参考file协议的内存,使用大小32768,而g_ptr是不同一片内存的
    avio_out =avio_alloc_context((unsigned char *)g_ptr1, IO_BUFFER_SIZE, 1,
                NULL, NULL, write_packet, seek); 
    if (!avio_out)
    {
        printf( “avio_alloc_context failed\n”);
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    // 分配AVFormatContext
    // out_filename 根据输出文件扩展名来判断格式
    // 注意该函数会分配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);

    for (int i = 0; i < (int)ifmt_ctx->nb_streams; i++)
    {
        //根据输入流创建输出流(Create output AVStream according to input AVStream)
        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的设置(Copy the settings of 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);

    //打开输出文件(Open output file)
    // !!! 存在于内存,不需要明确指定打开,这种形式是针对文件或网络协议的
    /*
    if (!(ofmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf( “Could not open output file ‘%s’: %s\n”, out_filename, strerror(errno));
            goto end;
        }
    }
    */

    //写文件头(Write file header)
    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(Get an AVPacket)
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;
        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];

        / copy packet /
        //转换PTS/DTS(Convert 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;

        //写入(Write)
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            printf( “Error muxing packet\n”);
            break;
        }
        av_free_packet(&pkt);
    }

    //写文件尾(Write file trailer)
    av_write_trailer(ofmt_ctx);

end:
    avformat_close_input(&ifmt_ctx);

    avformat_free_context(ofmt_ctx);

    #ifdef USE_MEM
    write(g_fd, g_ptr, g_nRealLen);
    #endif
    av_freep(&avio_out);
    av_freep(&g_ptr);

    close(g_fd);
    return ret;
}

参考资料:
http://blog.csdn.net/leixiaohua1020/article/details/25422685
http://stackoverflow.com/questions/13838841/decode-a-video-file-from-memory-using-libav
http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context

李迟 2015.6.9 周二 中午

	<div>

如果本文对阁下有帮助,不妨赞助笔者以输出更多好文章,谢谢!

donate




猜你喜欢

转载自blog.csdn.net/u014799914/article/details/86637276