音视频技术应用(18)- 裁剪一定长度的视频数据并进行重封装

示例code:

#include <iostream>
#include <thread>

using namespace std;

extern "C" { // 指定函数是C语言函数,以C语言的方式去编译
#include <libavformat/avformat.h>
}

// 以预处理指令的方式导入库
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")

void PrintErr(int err)
{
    char buf[1024] = {0};
    av_strerror(err, buf, sizeof(buf) - 1);
    cout << buf << endl;
}

#define CERR(err) if (err != 0) {PrintErr(err); return -1;} 

int main()
{
    // 打开媒体文件
    const char* url = "v1080.mp4";

    // 解封装的输入上下文
    AVFormatContext* ic = nullptr;
    auto re = avformat_open_input(&ic, url, 
        NULL,                        // 封装器格式,NULL表示自动探测 会根据文件名和文件头探测 
        NULL                        // 如果打开的媒体文件是RTSP格式,则需要进行设置
    );
    CERR(re);

    // 获取媒体信息 无头部格式
    re = avformat_find_stream_info(ic, NULL);
    CERR(re);

    // 打印封装信息
    av_dump_format(ic, 0, url, 
        0                            // 0 代表上下文是输入,1 代表上下文是输出
    );

    AVStream* as = nullptr;            // 表示音频流
    AVStream* vs = nullptr;            // 表示视频流
    for (int i = 0; i < ic->nb_streams; i++)
    {
        if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            as = ic->streams[i];
            cout << "================ 音频 ================" << endl;
            cout << "sample_rate: " << as->codecpar->sample_rate << endl;
        }
        else if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            vs = ic->streams[i];
            cout << "================ 视频 ================" << endl;
            cout << "width: " << vs->codecpar->width << endl;
            cout << "height: " << vs->codecpar->height << endl;
        }
    }

    /////////////////////////////////////////////////////////////////////
    /// 封装处理
    const char* out_url = "test_mux.mp4";
    AVFormatContext* ec = nullptr;
    avformat_alloc_output_context2(&ec, NULL, NULL, 
        out_url                                            // 根据文件名推测封装格式
    );
    CERR(re);

    // 添加视频流和音频流
    auto mvs = avformat_new_stream(ec, NULL);            // 视频流
    auto mas = avformat_new_stream(ec, NULL);            // 音频流

    // 打开输入输出的IO
    re = avio_open(&ec->pb, out_url, AVIO_FLAG_WRITE);
    CERR(re);

    // 设置编码的音视频参数
    if (vs)
    {
        mvs->time_base = vs->time_base;                                // 使用源视频的时间基数
        avcodec_parameters_copy(mvs->codecpar, vs->codecpar);        // 从解封装复制编码器参数
    }

    if (as)
    {
        mas->time_base = as->time_base;
        avcodec_parameters_copy(mas->codecpar, as->codecpar);
    }

    // 写入文件头
    re = avformat_write_header(ec, NULL);
    CERR(re);

    // 打印输出上下文
    av_dump_format(ec, 0, out_url, 1);                                // 1 代表是输出

    /////////////////////////////////////////////////////////////////////
    /// 截取10s~20s之间的音频和视频

    double begin_sec = 10.0;                    // 截取的开始时间
    double end_sec = 20.0;                        // 截取的结束时间

    long long begin_pts = 0;                    // 截取的开始pts(视频)
    long long begin_audio_pts = 0;                // 截取的开始pts(音频)

    long long end_pts = 0;

    // 将截取时间换算成pts, 以视频流为准
    if (vs && vs->time_base.num > 0)
    {
        // 运算依据:
        // 最终转换的pts = 秒数 / 时间基数 = sec / (num / den) = sec * (den / num)
        double t = (double)vs->time_base.den / (double)vs->time_base.num;
        begin_pts = begin_sec * t;
        end_pts = end_sec * t;
    }

    if (as && as->time_base.num > 0)
    {
        begin_audio_pts = begin_sec * ((double)as->time_base.den / (double)as->time_base.num);
    }

    // Seek 输入的媒体,移动到第10s的关键帧位置
    if (vs)
    {
        re = av_seek_frame(
            ic,                                            // 输入媒体的上下文 
            vs->index,                                    // 以谁为基准进行seek
            begin_pts,                                    // 从哪个位置开始seek
            AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD    // 优先移动到关键帧位置(AVSEEK_FLAG_FRAME), 如果指定的位置没有关键帧,则尝试向右移动(AVSEEK_FLAG_BACKWARD)
        );
        CERR(re);
    }

    AVPacket pkt;
    for (;;)
    {
        re = av_read_frame(ic, &pkt);
        if (re != 0)
        {
            PrintErr(re);
            break;
        }
        
        AVStream* in_stream = ic->streams[pkt.stream_index];
        AVStream* out_stream = nullptr;

        long long offset_pts = 0;                // 偏移pts, 用于截断开头的pts运算

        if (vs && pkt.stream_index == vs->index)
        {
            cout << "视频: ";
            // 超过20秒即退出,只截取并存储10s~20s之间的数据
            if (pkt.pts > end_pts)
            {
                av_packet_unref(&pkt);            // 注意在退出前需要释放AVPacket, 否则容易内存泄漏
                break;
            }

            out_stream = ec->streams[0];
            offset_pts = begin_pts;
        }
        else if (as && pkt.stream_index == as->index)
        {
            cout << "音频: ";
            out_stream = ec->streams[1];
            offset_pts = begin_audio_pts;
        }
        cout << pkt.pts << " : " << pkt.dts << " : " << pkt.size << endl;

        // 重新计算pts, dts, duration
        if (out_stream)
        {
            pkt.pts = av_rescale_q_rnd(pkt.pts - offset_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 - offset_pts, 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;

        // 写入音视频数据, 会自动清理Packet
        re = av_interleaved_write_frame(ec, &pkt);
        if (re != 0)
        {
            PrintErr(re);
        }

        // 这里一定要注意AVPacket 内部空间的释放,因为调用av_read_frame后并不会释放前一次申请的内部数据空间
        // av_packet_unref(&pkt);
        // this_thread::sleep_for(100ms);
    }

    // 写入结尾 (包含文件的偏移索引)
    re = av_write_trailer(ec);
    if (re != 0)
    {
        PrintErr(re);
    }

    avformat_close_input(&ic);
    
    avio_closep(&ec->pb);
    
    avformat_free_context(ec);
    ec = nullptr;

    return 0;
}

<完>

上一篇:实现图片懒加载的方法


下一篇:CodeForces 631D Messenger