IOS-FFmpeg解封装与封装

作者: Johnny_Wu | 来源:发表于2019-04-16 09:41 被阅读0次

解封装跟封装一块讨论,更容易理解,也更好调试。
解封装:一个视频文件,比如mp4,解封装后,可以得到具体的流AVStream(视频流,音频流,文字流等)
封装:把各种流组成一个视频文件
所以你可以看到demo代码总体逻辑为:先解封装,得到各种流后,再根据这些流封装成视频文件。
代码参考:http://ffmpeg.org/doxygen/trunk/remuxing_8c-example.html

头文件:

#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>

比较懒,全部的头文件都加进来了

主要代码:

- (int)mainFunc{
    av_register_all();
    avcodec_register_all();
    
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename;
    int ret, i;
    int stream_index = 0;
    int *stream_mapping = NULL;
    int stream_mapping_size = 0;
    
    //input
    NSString *filePath = [CommonFunc getDocumentWithFile:@"movie.mp4"];
    in_filename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];
    //output
    filePath = [CommonFunc getDefaultPath:@"movieOut.mp4"];
    out_filename = [filePath cStringUsingEncoding:NSASCIIStringEncoding];

    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    stream_mapping_size = ifmt_ctx->nb_streams;
    stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
    if (!stream_mapping) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    ofmt = ofmt_ctx->oformat;
    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *out_stream;
        AVStream *in_stream = ifmt_ctx->streams[I];
        AVCodecParameters *in_codecpar = in_stream->codecpar;
        if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_mapping[i] = -1;
            continue;
        }
        stream_mapping[i] = stream_index++;
        //这里创建的流,默认是从0开始累加的,所以不一定跟ifmt_ctx流的位置对应
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy codec parameters\n");
            goto end;
        }
//        out_stream->codecpar->codec_tag = 0;
        //可以在avformat_write_header配置好time_base,要不然执行完avformat_write_header后,就会有默认的time_base,因为两个流的time_base不一样,所以后面的转换设置pkt.pts pkt.dts pkt.duration。time_base一样就不需要设置
        out_stream->time_base = in_stream->time_base;
    }
    av_dump_format(ofmt_ctx, 0, out_filename, 1);
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }
    //执行这个后,out_stream->time_base修改为对应格式的默认值,比如mp4,默认为90k
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }
    while (1) {
        AVStream *in_stream, *out_stream;
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;
        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        if (pkt.stream_index >= stream_mapping_size ||
            stream_mapping[pkt.stream_index] < 0) {
            av_packet_unref(&pkt);
            continue;
        }
        pkt.stream_index = stream_mapping[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
//        out_stream->time_base = in_stream->time_base;
        log_packet(ifmt_ctx, &pkt, "in");
        /* copy packet */
//        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
//        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, 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;
        log_packet(ofmt_ctx, &pkt, "out");
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }
    av_write_trailer(ofmt_ctx);
end:
    avformat_close_input(&ifmt_ctx);
    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    av_freep(&stream_mapping);
    if (ret < 0 && ret != AVERROR_EOF) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }
    return 0;
}

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

解封装部分:

AVFormatContext *ifmt_ctx = NULL;
avformat_open_input(&ifmt_ctx, in_filename, 0, 0)
avformat_find_stream_info(ifmt_ctx, 0)
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *out_stream;
      //得到了各个流
        AVStream *in_stream = ifmt_ctx->streams[I];
}

解封装就这么简单。
可以根据in_stream->codecpar->codec_type判断具体的流的类型,之前版本使用in_stream->codec,最新的提示弃用了这个结构,改为in_stream->codecpar,当然现在你也还是可以用in_stream->codec的。其实in_stream->codec就是之前说的AVCodecContext,记录编解码的一些信息。没有它,就算得到了流,也没意义,因为无法具体去解码。

封装部分:

//创建输出的封装上下文
AVFormatContext *ofmt_ctx = NULL
//根据输出文件名,得到具体的ofmt_ctx内容
//当然这里得到的只是很少的内容
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
//从ofmt_ctx创建流,流的index是从0开始累加的
out_stream = avformat_new_stream(ofmt_ctx, NULL);
//从对应的输入流中拷贝一份codecpar给对应的输出流
//这就确定了,这是什么类型的流(视频流,音频流等)
avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
//time_base也用输入流的,每个流都有它对应的time_base,之前有讨论过time_base的意思
out_stream->time_base = in_stream->time_base;
//下面都是具体的输出操作
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE)
avformat_write_header(ofmt_ctx, NULL)
av_interleaved_write_frame(ofmt_ctx, &pkt)
av_write_trailer(ofmt_ctx)

存在于流中的,代表具体的编码的数据内容的是AVPacket结构体
AVPacket存储的是编码后的数据,如果是mp4,就有可能存的是h264编码数据

所以你看代码:
AVPacket pkt;
av_read_frame(ifmt_ctx, &pkt)
从输入流读出来的是pkt。这里也好理解。
然后就是把pkt写入到输出ofmt_ctx 的对应的流中:
av_interleaved_write_frame(ofmt_ctx, &pkt)
那么,我怎么知道写入到哪个流?
根据pkt.stream_index值,就知道是写入哪个流了

代码还用到了stream_mapping,这个不是主要代码,就是为了用输入流的index找到对应的新创的输出流的index,因为他们的index不一定相等。

还有很有意思的,无关要紧的一段输出log代码:

AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);

这里面还是包含了不少信息,我简单讲讲:
pkt->pts:可以认为是帧的展示时间,就是在显示器的显示的时间
pkt->dts:帧的解码时间,为什么要加入这个呢,不是根据pts时间,然后同步解码就可以啦。其实有些帧是要根据前后的帧才可以解码的,如B帧,那么得到B帧后,是不是得先去解B帧后面的帧。
pkt->duration:帧的时长
time_base:pts、dts、duration的时间单位,之前有讲到过
截取了段输出内容:


屏幕快照 2019-04-15 下午8.24.37.png

pts:1024 然后转化为秒就是0.0666667,具体算法是:pts*timebase = 1024*(1/15360)= 0.0666667
就是说着一帧在0.0666667的时候播放出来,那么后面我们就可以通过控制timebase来决定pts了,那么就相当于控制了视频的播放速度,总时长等。
最后别忘了进行资源的释放与文件的关闭。

相关文章

  • IOS-FFmpeg解封装与封装

    解封装跟封装一块讨论,更容易理解,也更好调试。解封装:一个视频文件,比如mp4,解封装后,可以得到具体的流AVSt...

  • 五、IOS-FFmpeg解封装、解码、编码、封装

    结合前面的知识,这次把FFmpeg的解封装、解码、编码和封装整合到一块。代码仓库:https://github.c...

  • 模块

    容器------数据的封装 函数------语句的封装 类 ------方法与属性的封装 模块------模块就...

  • 封装、继承、多态

    封装:隐藏实现细节通过公共方法向外暴露该对象的功能作用:解耦 封装:解耦隐藏对象的实现细节通过公共方法来向外暴露该...

  • 数据结构-持续更新

    数据结构与算法 1.Stack 封装的代码: 2.Queue 封装的代码: 3.LinkedList 封装的代码:...

  • 02类与对象:封装,属性方法隐藏和property装饰器

    封装 0 引入 面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。封装指的就是把数据与功能都...

  • 学习ffmpeg 结构体之间关系

    FFmpeg 有多个重要的结构体,解协议,解分装,解码,解封装。解协议:http,rstp,rtmp,mms。AV...

  • JavaScript面向对象与设计模式

    1. 面向对象 1.1 封装 封装的目的在于将信息隐藏。广义的封装不仅包括封装数据和封装实现,还包括封装类型和封装...

  • 【计算和控制流】28、代码组织:函数(def)

    目录一、封装一个功能二、定义与调用函数三、变量的作用域四、函数小技巧 一、封装一个功能 封装容器是对数据的封装函数...

  • Android-IM架构设计

    ###1. 架构总览 ###2. 模块介绍 ####2.1 协议封装与任务流程 #####1) 协议与任务的封装 ...

网友评论

    本文标题:IOS-FFmpeg解封装与封装

    本文链接:https://www.haomeiwen.com/subject/oqepwqtx.html