FFmpeg封装格式处理4-转封装例程

简介: 转封装是将一种封装格式转换为另一种封装格式,不涉及编解码操作,转换速度非常快。

作者:叶余

来源:https://www.cnblogs.com/leisure_chn/p/10506662.html


FFmpeg 封装格式处理相关内容分为如下几篇文章:

[1]. FFmpeg 封装格式处理-简介

[2]. FFmpeg 封装格式处理-解复用例程

[3]. FFmpeg 封装格式处理-复用例程

[4]. FFmpeg 封装格式处理-转封装例程

5. 转封装例程

转封装是将一种封装格式转换为另一种封装格式,不涉及编解码操作,转换速度非常快。

image.png

5.1 源码

源码修改自 FFmpeg 4.1 自带的例程 remuxing.c。代码非常简短:

#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
int main(int argc, char **argv)
{
    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;
    if (argc < 3) {
        printf("usage: %s input output\n"
               "API example program to remux a media file with libavformat and libavcodec.\n"
               "The output format is guessed according to the file extension.\n"
               "\n", argv[0]);
        return 1;
    }
    in_filename  = argv[1];
    out_filename = argv[2];
    // 1. 打开输入
    // 1.1 读取文件头,获取封装格式相关信息
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        printf("Could not open input file '%s'", in_filename);
        goto end;
    }
    // 1.2 解码一段数据,获取流相关信息
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        printf("Failed to retrieve input stream information");
        goto end;
    }
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    // 2. 打开输出
    // 2.1 分配输出 ctx
    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;
    }
    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++;
        // 2.2 将一个新流(out_stream)添加到输出文件(ofmt_ctx)
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            printf("Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        // 2.3 将当前输入流中的参数拷贝到输出流中
        ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
        if (ret < 0) {
            printf("Failed to copy codec parameters\n");
            goto end;
        }
        out_stream->codecpar->codec_tag = 0;
    }
    av_dump_format(ofmt_ctx, 0, out_filename, 1);
    if (!(ofmt->flags & AVFMT_NOFILE)) {    // TODO: 研究 AVFMT_NOFILE 标志
        // 2.4 创建并初始化一个 AVIOContext,用以访问 URL(out_filename)指定的资源
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            printf("Could not open output file '%s'", out_filename);
            goto end;
        }
    }
    // 3. 数据处理
    // 3.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;
        // 3.2 从输出流读取一个 packet
        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];
        /* copy packet */
        // 3.3 更新 packet 中的 pts 和 dts
        // 关于 AVStream.time_base 的说明:
        // 输入:输入流中含有 time_base,在 avformat_find_stream_info()中可取到每个流中的 time_base
        // 输出:avformat_write_header()会根据输出的封装格式确定每个流的 time_base 并写入文件中
        // AVPacket.pts 和 AVPacket.dts 的单位是 AVStream.time_base,不同的封装格式其 AVStream.time_base 不同
        // 所以输出文件中,每个 packet 需要根据输出封装格式重新计算 pts 和 dts
        av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        // 3.4 将 packet 写入输出
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            printf("Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }
    // 3.5 写输出文件尾
    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) {
        printf("Error occurred: %s\n", av_err2str(ret));
        return 1;
    }
    return 0;
}

5.2 编译

源文件为 remuxing.c,在 SHELL 中执行如下编译命令:

gcc -o remuxing remuxing.c -lavformat -lavcodec -lavutil -g

生成可执行文件 remuxing

5.3 验证

测试文件下载:tnshih.flv

image.png

先看一下测试用资源文件的格式:

think@opensuse> ffprobe tnliny.flv 
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnliny.flv':
  Metadata:
    encoder         : Lavf58.20.100
  Duration: 00:02:26.54, start: 0.000000, bitrate: 446 kb/s
    Stream #0:0: Video: h264 (High), yuv420p(progressive), 800x450, 25 fps, 25 tbr, 1k tbn, 50 tbc
    Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp

运行如下命令进行测试:

./remuxing tnliny.flv tnliny.ts

使用 ffprobe 检测输出文件正常。使用 ffplay 播放输出文件正常,播放效果和原始的测试文件一致。


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

阿里云视频云@凡科快图.png

相关文章
|
18天前
|
存储 编解码 算法
探索FFmpeg复用:深入理解媒体数据的组织与封装(三)
探索FFmpeg复用:深入理解媒体数据的组织与封装
48 0
|
18天前
|
存储 编解码 数据处理
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(三)
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码
55 0
|
18天前
|
存储 编解码 数据处理
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(二)
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码
58 0
|
7月前
|
存储 编解码 Cloud Native
音视频 ffmpeg命令转封装
音视频 ffmpeg命令转封装
|
18天前
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
《FFmpeg开发实战》一书中,第10章示例程序playaudio.c原本仅支持mp3和aac音频播放。为支持ogg、amr、wma等非固定帧率音频,需进行三处修改:1)当frame_size为0时,将输出采样数量设为512;2)遍历音频帧时,计算实际采样位数以确定播放数据大小;3)在SDL音频回调函数中,确保每次发送len字节数据。改进后的代码在chapter10/playaudio2.c,可编译运行播放ring.ogg测试,成功则显示日志并播放铃声。
21 1
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
|
18天前
|
编解码 API 数据处理
【摄像头数据处理】摄像头数据处理:使用FFmpeg合并、编码和封装视频流
【摄像头数据处理】摄像头数据处理:使用FFmpeg合并、编码和封装视频流
74 0
|
18天前
|
存储 编解码 安全
探索FFmpeg复用:深入理解媒体数据的组织与封装(二)
探索FFmpeg复用:深入理解媒体数据的组织与封装
63 0
|
18天前
|
存储 编解码 算法
探索FFmpeg复用:深入理解媒体数据的组织与封装(一)
探索FFmpeg复用:深入理解媒体数据的组织与封装
62 0
|
18天前
|
存储 缓存 编解码
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(一)
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码
62 0
|
18天前
|
存储 算法 前端开发
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
48 0