ffmpeg合并音频和视频
ffmpeg合并音频和视频
命令行
ffmpeg -i video.m4s -i audio.m4s -acodec copy -vcodec copy out.mp4
使用ffmpeg的api
extern "C" {
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
#include "libavcodec/avcodec.h"
}
extern "C"
int mergeVideo(const char* audio_src, const char* video_src, const char* dst) {
int ret = 0;
printf("mergeVideo enter\n");
printf("%s\n", audio_src);
printf("%s\n", video_src);
printf("%s\n", dst);
// 注册FFmpeg库中的所有编解码器、格式和协议(高版本不再需要注册)
// av_register_all();
// 创建输入音频和视频文件的AVFormatContext
AVFormatContext *inputAudioFormatCtx = NULL;
AVFormatContext *inputVideoFormatCtx = NULL;
if (avformat_open_input(&inputAudioFormatCtx, audio_src, NULL, NULL) != 0 ||
avformat_find_stream_info(inputAudioFormatCtx, NULL) < 0) {
printf("无法打开音频输入文件\n");
return -1;
}
if (avformat_open_input(&inputVideoFormatCtx, video_src, NULL, NULL) != 0 ||
avformat_find_stream_info(inputVideoFormatCtx, NULL) < 0) {
printf("无法打开视频输入文件\n");
return -1;
}
// 创建输出文件的AVFormatContext
AVFormatContext *outputFormatCtx = NULL;
// if (avformat_alloc_output_context2(&outputFormatCtx, NULL, "mpegts", dst) < 0) {
if (avformat_alloc_output_context2(&outputFormatCtx, NULL, NULL, dst) < 0) {
printf("无法创建输出文件\n");
return -1;
}
// 遍历输入音频流
int audio_index = av_find_best_stream(inputAudioFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVStream* st1 = inputAudioFormatCtx->streams[audio_index];
// const AVCodec* codec = NULL;
// codec = avcodec_find_decoder(st1->codecpar->codec_id);
// if (!codec)
// {
// fprintf(stderr, "Codec not found\n");
// exit(1);
// }
// AVCodecContext* codec_ctx = NULL;
// codec_ctx = avcodec_alloc_context3(codec);
// if (!codec_ctx)
// {
// exit(1);
// }
// avcodec_parameters_to_context(codec_ctx, inputAudioFormatCtx->streams[audio_index]->codecpar);
// if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
// {
// return -1;
// }
// 遍历输入视频流
int video_index = av_find_best_stream(inputVideoFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st2 = inputVideoFormatCtx->streams[video_index];
// const AVCodec* codec2 = NULL;
// codec2 = avcodec_find_decoder(st2->codecpar->codec_id);
// if (!codec2)
// {
// fprintf(stderr, "Codec not found\n");
// exit(1);
// }
// AVCodecContext* codec_ctx2 = NULL;
// codec_ctx2 = avcodec_alloc_context3(codec2);
// if (!codec_ctx2)
// {
// exit(1);
// }
// avcodec_parameters_to_context(codec_ctx2, inputVideoFormatCtx->streams[video_index]->codecpar);
// if ((ret = avcodec_open2(codec_ctx2, codec2, NULL) < 0))
// {
// return -1;
// }
// 创建输出视频流
// 每创建一个流,ctx->nb_streams数量就会加1
AVStream* out1 = avformat_new_stream(outputFormatCtx, NULL);
if (!out1)
{
return -1;
}
//复制配置信息
AVCodecParameters* codecpar = inputAudioFormatCtx->streams[audio_index]->codecpar;
avcodec_parameters_copy(out1->codecpar, codecpar);
out1->codecpar->codec_tag = 0;
//创建输出视频流
AVStream* out2 = avformat_new_stream(outputFormatCtx, NULL);
if (!out2)
{
return -1;
}
//复制配置信息
AVCodecParameters* codecpar2 = inputVideoFormatCtx->streams[video_index]->codecpar;
avcodec_parameters_copy(out2->codecpar, codecpar2);
out2->codecpar->codec_tag = 0;
// 打开输出文件
if (!(outputFormatCtx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatCtx->pb, dst, AVIO_FLAG_WRITE) < 0) {
printf("无法打开输出文件\n");
return -1;
}
}
// 写入文件头
if (avformat_write_header(outputFormatCtx, NULL) < 0) {
printf("无法写入文件头\n");
return -1;
}
// 复制音频和视频数据
AVPacket pkt1, pkt2;
int ret1 = 0, ret2 = 0;
// 音视频合成
while (1) {
if (ret1 < 0 && ret2 < 0) {
break;
}
// printf("ret1:%d\tret2:%d\tst->cur_dts:%ld\tst2->cur_dts:%ld\tpkt1.dts:%ld\tpkt2.dts:%ld\ttime_base:%f %f\tdst:%s\t\n",
// ret1, ret2, st1->cur_dts, st2->cur_dts, pkt1.dts, pkt2.dts, av_q2d(st1->time_base), av_q2d(st2->time_base), dst);
// printf("avg_frame_rate:%f %f\tr_frame_rate:%f %f\tduration:%ld %ld\n", av_q2d(st1->avg_frame_rate),
// av_q2d(st1->r_frame_rate), av_q2d(st2->avg_frame_rate), av_q2d(st2->r_frame_rate), pkt1.duration, pkt2.duration);
// 根据时间轮流写音频和视频,音频和视频在现实世界长度相同不意味着存储的帧数相同
// PTS:Presentation Time Stamp。PTS 主要用于度量解码后的视频帧什么时候被显示出来。
// DTS:Decode Time Stamp。DTS 主要是标识读入内存中的Bit流在什么时候开始送入解码器中进行解码。
// 有时读到的数据包的pts是空的,所以使用dts比较时间
// android上st1没有cur_dts成员,可以使用pkt.pts,但是裸流pts和dts都是空的
// 可能需要计算pts和dts并对pkt进行设置,这个可能有帮助:https://zhuanlan.zhihu.com/p/468346396
// if ((st1->cur_dts < st2->cur_dts && ret1 >= 0) || ret2 < 0) {
if ((pkt1.dts < pkt2.dts && ret1 >= 0) || ret2 < 0) {
// 音频时间落后,或视频已写完时写音频
ret1 = av_read_frame(inputAudioFormatCtx, &pkt1);
if (ret1 < 0) {
continue;
}
if (pkt1.stream_index == audio_index) {
pkt1.stream_index = 0; // 设置音频流索引
av_packet_rescale_ts(&pkt1, st1->time_base, out1->time_base);
pkt1.pos = -1;
ret = av_interleaved_write_frame(outputFormatCtx, &pkt1); // 写入音频包
if (ret < 0) {
break;
}
}else {
av_packet_unref(&pkt1);
}
} else {
// 视频时间落后,或音频已写完时写视频
ret2 = av_read_frame(inputVideoFormatCtx, &pkt2);
if (ret2 < 0) {
continue;
}
if (pkt2.stream_index == video_index) {
pkt2.stream_index = 1; // 设置视频流索引
// 单纯的音频或视频文件可能使用的都是0通道,既有音频又有视频的情况,不能使用同一个通道
av_packet_rescale_ts(&pkt2, st2->time_base, out2->time_base); // 重新设置时间基,否则可能导致新视频播放时间不准
pkt2.pos = -1;
ret = av_interleaved_write_frame(outputFormatCtx, &pkt2); // 写入视频包
if (ret < 0) {
break;
}
}else {
av_packet_unref(&pkt2);
}
}
}
// 写入文件尾
av_write_trailer(outputFormatCtx);
// 释放资源
avformat_close_input(&inputAudioFormatCtx);
avformat_close_input(&inputVideoFormatCtx);
avformat_free_context(outputFormatCtx);
return ret;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(merge LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_COMPILER g++)
set(CMAKE_BUILD_TYPE "RELEASE")
# set(CMAKE_BUILD_TYPE "DEBUG")
# set(FFMPEG_LIBS_DIR /usr/lib/x86_64-linux-gnu)
# set(FFMPEG_HEADERS_DIR /usr/include/x86_64-linux-gnu)
set(FFMPEG_LIBS_DIR /home/light/FFmpeg-master-linux/FFmpeg-master/build/x86-64/include)
set(FFMPEG_HEADERS_DIR /home/light/FFmpeg-master-linux/FFmpeg-master/build/x86-64/lib)
include_directories(${FFMPEG_HEADERS_DIR})
link_directories(${FFMPEG_LIBS_DIR})
set(FFMPEG_LIBS libavcodec.so libavformat.so libswscale.so libavdevice.so libavutil.so)
include_directories(include/)
aux_source_directory(src/ srcfiles)
add_executable(merge main.cpp ${srcfiles})
target_link_libraries(merge ${FFMPEG_LIBS})
install(TARGETS merge RUNTIME DESTINATION bin)