ffmpeg实现视频的合成与分割

视频合成与分割程序使用    

    作者开发了一款软件,可以实现对视频的合成和分割,界面如下: 下载该程序

     播放时,可以选择多个视频源;在选中“保存视频”情况下,会将多个视频源合成一个视频。如果只取一个视频源中一段视频,就实现了视频分割。

      对视频的处理采用了ffmpeg库。作者在此库的基础上,做了进一步封装,使用起来更加简便。

 底层处理逻辑可用如下函数表示

bool InitVideo();
bool AddImage(unsigned char* imageFileBuffer, int bufferSize);
bool CloseVideo();

    可见底层函数是十分简洁的;  但是ffmpeg函数调用复杂,使用起来不便; 将ffmpeg封装亦非易事;本文就讲述对ffmpeg封装的过程。

视频编码与解码

    对视频的处理分为两种:解码和编码。视频播放属于解码,视频生成属于编码。视频播放方面的文章和例子很多;我也写过一篇文章《使用Emgu.CV开发视频播放器简述》

      视频其实就是连续的图片,编码的作用就是压缩图片,减小视频文件的占用。可以把视频文件想象成容器,把一些列图片放入容器,经过编码,生成标准格式的视频文件(如mp4),这个过程就是编码;

      把不同视频来源的图片放入容器,就实现了视频的合成;把视频中某段包含的图片放入容器,就实现了视频的分割。只要实现了对多个图片到视频的编码,就实现了视频的合成和分割。

 

初始化编码器,包括选择编码器,生成输入流,写入文件头等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
bool ImageToVideo::InitVideo()
{
    InitFfmpeg();
 
    AVFormatContext* pFormatCtx = NULL;
    _errnum = avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, _destVideoFileName.c_str());
    if (_errnum < 0)
    {
        av_strerror(_errnum, _errbuf, sizeof(_errbuf));
        return false;
    }
 
    _initFree.pFormatCtx = pFormatCtx;
 
    // h264视频编码器
    const AVCodec* vcodec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_H264);
    if (!vcodec)
    {
        return false;
    }
 
    // 创建编码器上下文
    AVCodecContext* pVideoCodecCtx = avcodec_alloc_context3(vcodec);
    if (!pVideoCodecCtx)
    {
        return false;
    }
    _initFree.pVideoCodecCtx = pVideoCodecCtx;
 
    // 比特率、宽度、高度
    pVideoCodecCtx->bit_rate = 4000000;
    pVideoCodecCtx->width = _videoWidth; // 视频宽度
    pVideoCodecCtx->height = _videoHeight; // 视频高度
    // 时间基数、帧率
    pVideoCodecCtx->time_base = { 1, 25 };
    pVideoCodecCtx->framerate = { 25, 1 };
    // 关键帧间隔
    pVideoCodecCtx->gop_size = 10;
    // 不使用b帧
    pVideoCodecCtx->max_b_frames = 0;
    // 帧、编码格式
    pVideoCodecCtx->pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P;
    pVideoCodecCtx->codec_id = AVCodecID::AV_CODEC_ID_H264;
    // 预设:快速
    av_opt_set(pVideoCodecCtx->priv_data, "preset", "superfast", 0);
    // 全局头
    pVideoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
 
    _errnum = avcodec_open2(pVideoCodecCtx, vcodec, NULL);
    if (_errnum < 0)
    {
        return false;
    }
 
    // 为封装器创建视频流
    AVStream* pVideoStream = avformat_new_stream(pFormatCtx, NULL);
    if (!pVideoStream)
    {
        return false;
    }
    _initFree.pVideoStream = pVideoStream;
 
    pVideoStream->codec->codec_tag = 0;
    pVideoStream->codecpar->codec_tag = 0;
    // 配置视频流的编码参数
    avcodec_parameters_from_context(pVideoStream->codecpar, pVideoCodecCtx);
 
    // 打开输出流IO
    _errnum = avio_open(&pFormatCtx->pb, _destVideoFileName.c_str(), AVIO_FLAG_WRITE); // 打开AVIO流
    if (_errnum < 0)
    {
        avio_close(pFormatCtx->pb);
        return false;
    }
 
    _errnum = avformat_write_header(pFormatCtx, NULL);
    if (_errnum < 0)
    {
        return false;
    }
 
    return true;
}

添加图片

1 对添加的图片缩放处理:

SwsContext* pSwsCtx = sws_getContext(
    imageWidth, imageHeight, srcFormat, 
    _initFree.pVideoCodecCtx->width, _initFree.pVideoCodecCtx->height, AVPixelFormat::AV_PIX_FMT_YUV420P, // 输出
    SWS_BICUBIC, 
    0, 0, 0);

2 发送frame ,接收编码后的packet

复制代码
vframe->pts = vpts++;
_errnum = avcodec_send_frame(_initFree.pVideoCodecCtx, vframe);
if (_errnum < 0)
{
    // cout << "avcodec_send_frame failed" << endl;
    av_frame_free(&vframe);
    return false;
}

// 视频编码报文
AVPacket* packet = av_packet_alloc();
int writeCount = 0;

while (true)
{
    _errnum = avcodec_receive_packet(_initFree.pVideoCodecCtx, packet);
    if (_errnum < 0 || packet->size <= 0)
    {
        int e1 = AVERROR_EOF;
        int e2 = AVERROR(EAGAIN);

        if (writeCount == 0)
        {
            av_frame_free(&vframe);
            av_packet_free(&packet);
            // cout << "avcodec_receive_packet failed" << endl;
            return false;
        }
        else
        {
            break;
        }
    }
复制代码

编码完成:写入文件尾数据,释放资源

复制代码
_errnum = av_write_trailer(_initFree.pFormatCtx);
if (_errnum != 0)
{
    return false;
}

if (pFormatCtx != NULL)
{
    avio_closep(&pFormatCtx->pb);
    avformat_close_input(&pFormatCtx);
}

if (pVideoCodecCtx != NULL)
{
    avcodec_close(pVideoCodecCtx);
    avcodec_free_context(&pVideoCodecCtx);
}


if (pSwsCtx != NULL)
{
    sws_freeContext(pSwsCtx);
}
复制代码

后记:对于视频的合成和分割,网上有不少这方面的文章,大都是讲述如何使用ffmpeg工具操作,这些方法不灵活,很难满足个性化的需求。本文从视频最基本的原理剖析,实现了图片合成视频的功能;这样就为上层丰富多彩的应用打开了大门。

       比如 将两个视频文件合成到一个播放画面;处理过程为:同时读取两个视频源的文件,将两个图片拼接,再放入视频容器。

posted @   源之缘-OFD解决方案  阅读(124)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
关注我
点击右上角即可分享
微信分享提示