QT 使用ffmpeg 学习3 转封装

一、概念

转封装是指在mp4、flv、avi等文件格式之间的转换。
常用见视频封装格式(容器):

1. AVI

容器 AVI(Audio Video Interleaved)即音视频交错格式.
AVI 符合 RIFF(Resource Interchange File Format)文件规范,使用四字符码 FOURCC(four-character code)来表征数据类型。AVI 的文件结构分为头部、主体和索引三部分。 主体中图像数据和声音数据是交互存放的,从尾部的索引可以索引跳到自己想放的位置。

AVI 本身只是提供了这么一个框架,内部的图像数据和声音数据格式可以是任意的编码形式。因为索引放在了文件尾部,所以在播网络流媒体时已属力不从心。一个很简单的例子,从网络上下载 AVI 文件,如果没有下载完成,是很难正常播放出来。

基本数据单元:

Chunks
typedef struct {
    DWORD dwFourCC
    DWORD dwSize      //data
    BYTE data[dwSize] // contains headers or video/audio data
} CHUNK;
 
Lists
typedef struct {
    DWORD dwList
    DWORD dwSize        //dwFourcc + data
    DWORD dwFourCC
    BYTE data[dwSize-4] // contains Lists and Chunks
} LIST;

如上可知,Chunks 数据块由一个四字符码、4 字节 data size(指下面的数据大小)以及数据组成

List 由四部分组成,四个字节四字符码(“list”)、4 字节数据大小(指后面列的两部分数据大小)、四字节 list 类型以及数据组成,与 Chunk 数据块不同的是,List 数据内容可以包含字块(Chunk 或 List)。

2. ts和ps封装

因为HDDVD以及BD之争,尽管两家在编码上都统一采用MPEG2/VC-1/H.264,可在封装格式上又有所分岐。DVD论坛官方所认可的HDDVD使用的是PS封装,即Program Stream(程序流),这和之前DVD所采用的MPEG2 Program Stream封装是一样的,PS流的后缀名是VOB以及EVO等。而BD在没有DVD论坛官方认证的情况下,自然不是PS封装,而是使用了MPEG2的另一封装TS封装,即Transport Stream(传输流),TS流的后缀名为TS。它们都是MPEG2系统部分的两个不同的语法结构,而在现在仅仅在作为封装使用。TS流对于PS流来说更易传输,不过由于其性质,也更易出错,所以在以前一般存储方面都是使用PS流,当然现在随着容错/纠错技术的提高,TS的适用范围越来越广。

现在网上大多流传以TS封装的HDTV remux版,PS封装只能在HDDVD原版才看到,所以我们来着重分析一下TS封装格式。

电视节目是你任何时候打开电视机都能解码(收看)的,所以,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。从结构上来说,TS是由头文件和主体所组成的,扩充过的TS流还包括时间戳。这样不管是什么格式的VBR音轨,都很容易通过时间戳来同步图像。当然,对新的声音格式来说,需要新的分离器,解码器来实现解码。目前在不断改进开发中。 

TS不像AVI,从诞生那天起,就考虑到了网络播放,所以很快成为了世界标准并广泛应用于电视台数字播放,手机等各个领域。
REMUX版本:
Remux的意思是无损的提取出HD-DVD和BluRay-DVD里面的视频数据和音频数据,封装到我们所熟悉的TC或AVI文件中。

ES

基本流 (Elementary Streams)是直接从编码器出来的数据流,也成为净荷数据。ES是编码后的视频流(比如H.264),音频数据流(如AAC),和其他编码数据流的统称。

ES是只包含一种内容的数据流(比如纯粹的视频或音频),每个ES都由若干个存取单元(AU)组成,每个视频AU或音频AU都是由头部和编码数据两部分组成,1个AU相当于编码的1幅视频图像或1个音频帧,也可以说,每个AU实际上是编码数据流的显示单元,即相当于解码的1幅视频图像或1个音频帧的取样。

PES

打包的ES(Packetized Elementary Streams),是用来传递ES的一种数据结构。是ES流经过 PES打包形成的数据流,即将ES流分组、打包、加入包头信息,是对ES流的第一次打包。

PTS - 显示时间戳(Presentation Time Stamp),用来表示显示单元出现在系统目标解码器的时间。

DTS - 解码时间戳(Decoding Time Stamp),用来表示将存取单元全部字节从解码缓存取走的时间。

PTS/DTS 这两个参数是解决音视频同步显示,防止解码器输入缓存上溢或下溢的关键。每一个 I帧 | P帧 | B帧 的包头都有一个PTS和DTS。

PS

  • 一个PS包 由若干个 PES 包组成,PS包头包含了同步信息与时钟恢复信息。
  • 一个PS包 最多可包含具有同一时钟基准的16个视频PES包和32个音频PES包。
    PS包是针对 ES净荷数据 的第二次封装。

3. MOV封装

MOV是Quicktime封装,与AVI相近年代,也有很多缺陷,现在很少有人使用。
HDRIP: 重新编码,即有损压缩过之后的视频。

4. MKV封装

MKV是Matroska的简称,比较常见的搭配是X264+MKV。MKV封装十分新颖,而且也非常开放。:

  • 可变帧率
  • 错误检测以及修复
  • 软字幕
  • 流式传输
  • 菜单
  • 强大兼容性
  • 开放性和跨平台性。

工作流程:
在这里插入图片描述

程序运行流程:
在这里插入图片描述

5. 其它一些概念

PTS

显示时间戳(Presentation Time Stamp),用来表示显示单元出现在系统目标解码器的时间。

DTS

解码时间戳(Decoding Time Stamp),用来表示将存取单元全部字节从解码缓存取走的时间。
PTS/DTS 这两个参数是解决音视频同步显示,防止解码器输入缓存上溢或下溢的关键。每一个 I帧 | P帧 | B帧 的包头都有一个PTS和DTS。

二、源码

pro文件

QT += core
QT -= gui

TARGET = remuxing
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp

INCLUDEPATH +="D:\\tools\\ffmpeg\\win32\\dev\\include"

LIBS += -LD:\tools\ffmpeg\win32\dev\lib -lavutil -lavformat -lavcodec -lavdevice -lavfilter -lpostproc -lswresample -lswscale

cpp

#include <QCoreApplication>
#include <QDebug>
#define __STDC_CONSTANT_MACORS

extern "C"
{
#include "libavformat/avformat.h"
}

int close();

AVOutputFormat *ofmt = NULL;

//输入输出各对应一个AVFormatContext
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
int ret;

int main(int argc,char* argv[]){
    QCoreApplication a(argc,argv);

    AVPacket pkt;
    const char *in_filename, *out_filename;
    int i;

    in_filename  = "D:/1.flv";      //输入文件名(Input file URL)
    out_filename = "D:/2.flv";      //输出文件名(Output file URL)

    qDebug() << "BEGIN" << endl;
    av_register_all();

    // 打开输入文件
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        qDebug() <<  "Could not open input file." << endl;
        return close();
    }
    // 获取媒体信息
    if((ret = avformat_find_stream_info(ifmt_ctx,0))<0){
        qDebug() <<  "Failed to retrieve input stream information" << endl;
        return close();
    }
    // 打印输入格式的详细信息,比如持续时间duration,比特率 bitrate,流 streams,容器 container, 程序programs,元数据 metadata, side data, 编解码   器codec ,时基 time base.
    // 第2个参数,流索引
    // 第3个参数,打印的url,如源文件或目标文件
    // 第4个参数,is_output 选择指定的context是0输入 还是1输出
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    // 分配一个用于输出格式的AVFormatContext
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if(!ofmt_ctx){
        qDebug() <<  "Could not create output context\n" << endl;
        ret = AVERROR_UNKNOWN;
        return close();
    }
    qDebug() <<  "hello" << endl;
    ofmt = ofmt_ctx->oformat;

    for(i=0;i<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;
            return close();
        }
        // 拷贝源AVCodecContext的设置到目的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");
            return close();
        }
        out_stream->codec->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //CODEC_FLAG_GLOBAL_HEADER找不到,使用AV_CODEC_FLAG_GLOBAL_HEADER;
    }
    //打印输出格式详细信息
    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'", out_filename);
             return close();
         }
     }
     //写文件头(Write file header)
     ret = avformat_write_header(ofmt_ctx, NULL);
     if (ret < 0) {
         printf( "Error occurred when opening output file\n");
         return close();
     }
     int frame_index=0;

     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;
             }
             printf("Write %8d frames to output file\n",frame_index);
             av_free_packet(&pkt);
             frame_index++;
         }
         //写文件尾(Write file trailer)
         av_write_trailer(ofmt_ctx);

    return a.exec();
}
// 释放资源
int close(){
    avformat_close_input(&ifmt_ctx);
    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    if (ret < 0 && ret != AVERROR_EOF) {
        printf( "Error occurred.\n");
        return -1;
    }
    return 0;
}

参考文章:
https://blog.csdn.net/qq_19923217/article/details/93774888

猜你喜欢

转载自blog.csdn.net/xundh/article/details/106046642