视频添加背景音乐ffmpeg(十八)

前言

有时候有一段无声的视频,想为其添加背景音乐,增加视频的丰富性。这种需求应该也很常见,本文目标就是为一段无声视频添加背景音乐,同时满足如下的需求:
1、声音可以添加在视频时间轴上的任意位置开始
2、以视频时间轴为参考,若音频时间最终超过视频的最大时间则截断
合并后新的音视频文件能够正常速率播放

实现思路分析

这里无声视频选择MP4,要添加的音频文件选择aac。其它格式类似;
我们知道,基于ffmpeg解封装时,音视频文件最终是由一系列AVPacket组成的;同理基于ffmpeg封装时,调用av_write_frame()函数将一些列音视频的AVPacket写入封装文件,最终得到的文件即可正常播放。那么对于无声的视频文件来说,它已有一路视频流,只需要为其再添加一路音频流即可。所以总体思路如下:
1、创建一个封装器AVFormatContext,添加一路视频流,用于存储源无声视频数据;添加一路音频流,用于存储要添加的音频数据
2、对于读取的源视频AVPacket,原封不动的写入到目标视频流中;对于读取的源音频AVPacket,其pts和dts的值都要加上想要加入视频时刻位置的时间戳,然后再写入到目标音频流中

流程图

image.png

实现代码

这里实现实现给任意一个无声的视频文件添加背景音乐的功能;为了简化处理,假设要加入的音频编码方式被视频容器格式支持。要求:
1、声音可以在视频时间轴上的任意位置开始
2、以视频时间轴为参考,若音频时间最终超过则截断
目的:合并后的文件能正确播放视频以及音频

 

using namespace std;
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
class Merge
{
public:
    Merge();
    ~Merge();
     
    /** 实现给任意一个无声的视频文件添加背景音乐的功能;为了简化处理,假设要加入的音频编码方式被视频容器格式支持。要求:
     *  1、声音可以在视频时间轴上的任意位置开始
     *  2、以视频时间轴为参考,若音频时间最终超过则截断
     *  目的:合并后的文件能正确播放视频以及音频
     */
    void addMusic();
    
private:
    
    string dstpath;
    AVFormatContext *in_fmt1;
    AVFormatContext *ou_fmt;
    int video1_in_index,audio1_in_index;
    int video_ou_index,audio_ou_index;
    int64_t next_video_pts,next_audio_pts;
     
    // 用于添加背景音乐
    int64_t start_pos;
    
 
    void releasesources();
};

start_pos 用来记录音频背景要加入到视频的参考时间戳位置,下面试addMusic的实现文件

 

void Merge::addMusic()
{
    string curFile(__FILE__);
    unsigned long pos = curFile.find("2-video_audio_advanced");
    if (pos == string::npos) {
        LOGD("not found fil");
        return;
    }
    /** 遇到问题:如果添加的音频文件为Mp3文件,则使用苹果的内置播放器及苹果手机没有声音。如果添加的是aac音频 则没有问题
     *  分析原因:暂时未知
     *  解决方案:暂时未知
     */
    string srcDic = curFile.substr(0,pos) + "filesources/";
    string srcpath  = srcDic + "test_1280x720_4.mp4";
//    string srcpath2 = srcDic + "test-mp3-1.mp3";
    string srcpath2 = srcDic + "test_441_f32le_2.aac";
    string dstpath = srcDic + "11_add_music.mp4";
    
    string start = "00:00:05";
    start_pos += stoi(start.substr(0,2))*3600;
    start_pos += stoi(start.substr(3,2))*60;
    start_pos += stoi(start.substr(6,2));
    
    in_fmt1 = NULL;// 用于解封装视频
    in_fmt2 = NULL;// 用于解封装音频
    ou_fmt = NULL;  //用于封装音视频
    
    // 打开视频文件
    if (avformat_open_input(&in_fmt1, srcpath.c_str(), NULL, NULL) < 0) {
        LOGD("1 avformat_open_input() fail");
        return;
    }
    if (avformat_find_stream_info(in_fmt1, NULL) < 0) {
        LOGD("1 avformat_find_stream_info");
        releasesources();
        return;
    }
    
    // 打开音频文件
    if (avformat_open_input(&in_fmt2, srcpath2.c_str(), NULL, NULL) < 0) {
        LOGD("2 avformat_open_input() fail");
        return;
    }
    if (avformat_find_stream_info(in_fmt2, NULL) < 0) {
        LOGD("2 avformat_find_stream_info");
        releasesources();
        return;
    }
    for (int i=0; i<in_fmt1->nb_streams; i++) {
        AVStream *stream = in_fmt1->streams[i];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video1_in_index = i;
            break;
        }
    }
    for (int i = 0; i<in_fmt2->nb_streams; i++) {
        AVStream *stream = in_fmt2->streams[i];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio1_in_index = i;
            break;
        }
    }
    
    // 打开输出文件的封装器
    if (avformat_alloc_output_context2(&ou_fmt, NULL, NULL, dstpath.c_str()) < 0) {
        LOGD("avformat_alloc_out_context2 ()");
        releasesources();
        return;
    }
    
    // 添加视频流并从输入源拷贝视频流编码参数
    AVStream *stream = avformat_new_stream(ou_fmt, NULL);
    video_ou_index = stream->index;
    if (avcodec_parameters_copy(stream->codecpar, in_fmt1->streams[video1_in_index]->codecpar) < 0) {
        releasesources();
        return;
    }
    // 如果源和目标文件的码流格式不一致,则将目标文件的code_tag赋值为0
    if (av_codec_get_id(ou_fmt->oformat->codec_tag, in_fmt1->streams[video1_in_index]->codecpar->codec_tag) != stream->codecpar->codec_id) {
        stream->codecpar->codec_tag = 0;
    }
    // 添加音频流并从输入源拷贝编码参数
    AVStream *a_stream = avformat_new_stream(ou_fmt, NULL);
    audio_ou_index = a_stream->index;
    if (avcodec_parameters_copy(a_stream->codecpar, in_fmt2->streams[audio1_in_index]->codecpar) < 0) {
        LOGD("avcodec_parameters_copy fail");
        releasesources();
        return;
    }
    if (av_codec_get_id(ou_fmt->oformat->codec_tag, in_fmt2->streams[audio1_in_index]->codecpar->codec_tag) != a_stream->codecpar->codec_id) {
        a_stream->codecpar->codec_tag = 0;
    }
    
    // 打开输出上下文
    if (!(ou_fmt->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open2(&ou_fmt->pb, dstpath.c_str(), AVIO_FLAG_WRITE, NULL, NULL) < 0) {
            LOGD("avio_open2() fail");
            releasesources();
            return;
        }
    }
    
    // 写入头文件
    if (avformat_write_header(ou_fmt, NULL) < 0) {
        LOGD("avformat_write_header()");
        releasesources();
        return;
    }
    

    AVPacket *v_pkt = av_packet_alloc();
    AVPacket *a_pkt = av_packet_alloc();
    // 写入视频
    int64_t video_max_pts = 0;
    while (av_read_frame(in_fmt1, v_pkt) >= 0) {
        if (v_pkt->stream_index == video1_in_index) {   // 说明是视频
            // 因为源文件和目的文件时间基可能不一致,所以这里要进行转换
            av_packet_rescale_ts(v_pkt, in_fmt1->streams[video1_in_index]->time_base, ou_fmt->streams[video_ou_index]->time_base);
            v_pkt->stream_index = video_ou_index;
            video_max_pts = max(v_pkt->pts, video_max_pts);
            LOGD("video pts %d(%s)",v_pkt->pts,av_ts2timestr(v_pkt->pts, &stream->time_base));
            if (av_write_frame(ou_fmt, v_pkt) < 0) {
                LOGD("1 av_write_frame < 0");
                releasesources();
                return;
            }
        }
    }

    int64_t start_pts = start_pos * a_stream->time_base.den;
    video_max_pts = av_rescale_q(video_max_pts, stream->time_base, a_stream->time_base);
    // 写入音频
    while (av_read_frame(in_fmt2, a_pkt) >= 0) {
        if (a_pkt->stream_index == audio1_in_index) {   // 音频
    
            // 源文件和目标文件的时间基可能不一致,需要转化
            av_packet_rescale_ts(a_pkt, in_fmt2->streams[audio1_in_index]->time_base, ou_fmt->streams[audio_ou_index]->time_base);
            // 保证以视频时间轴的指定时间进行添加,那么实际上就是改变pts及dts的值即可
            a_pkt->pts += start_pts;
            a_pkt->dts += start_pts;
            a_pkt->stream_index = audio_ou_index;
            LOGD("audio pts %d(%s)",a_pkt->pts,av_ts2timestr(a_pkt->pts, &a_stream->time_base));
            // 加入音频的时长不能超过视频的总时长
            if (a_pkt->pts >= video_max_pts) {
                break;
            }
            
            if (av_write_frame(ou_fmt, a_pkt) < 0) {
                LOGD("2 av_write_frame < 0");
                releasesources();
                return;
            }
        }
    }
    
    av_write_trailer(ou_fmt);
    LOGD("写入完毕");
    
    // 释放资源
    releasesources();
    
}

遇到问题

1、如果添加的音频文件为Mp3文件,则使用苹果的内置播放器及苹果手机没有声音。如果添加的是aac音频 则没有问题,如果是为MOV视频添加MP3音频文件,也不会有问题
分析原因:原因不详

后参数用ffmpeg命令为MP4视频添加mp3音乐背景,发现也有同样的问题,应该是ffmpeg对于mp4文件写入mp3编码格式码流方式和苹果设备不兼容。

项目地址

https://github.com/nldzsz/ffmpeg-demo

位于cppsrc目录下Merge.hpp/Merge.cpp文件中的
addMusic()函数中

 

image.png

项目下示例可运行于iOS/android/mac平台,工程分别位于demo-ios/demo-android/demo-mac三个目录下,可根据需要选择不同平台



作者:仙人掌D
链接:https://www.jianshu.com/p/52bf6365d153
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值