2017年,各种关于视频处理的功能层出不穷,现在基本上大多APP都有视频上传播放功能。
然而上传,播放可用的优秀框架有很多,我们在此就不说了。
本篇我们就做一个用ffmpeg给视频添加/更换背景音乐的功能
Image may be NSFW.
Clik here to view.
UI界面有两个输入框,第一个填入要加背景音乐的视频文件,第一个要添加音乐的文件。
然后点击按钮。
Image may be NSFW.
Clik here to view.
可见在手机根目录生成了一个合成的视频文件。试播一下,发现已经有了背景音乐。
其实现原理主要是剥开视频文件拿到视频裸流,然后拿到音频文件根据时间戳一帧一帧的封装成一个新的视频文件
下面是具体实现的代码逻辑。
首先项目中导入编译好的ffmpeg库,然后书写CMakeLists.txt链接这些库
Image may be NSFW.
Clik here to view.
然后编写界面,书写界面的逻辑。
主要代码如下:
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.button:
startDecode();
break;
default:
break;
}
}
private void startDecode() {
String folderurl= Environment.getExternalStorageDirectory().getPath();
String videoUrl=folderurl+"/"+editText1.getText().toString();
String musicUrl=folderurl+"/"+editText2.getText().toString();
Log.e("ws-----------videoUrl",videoUrl);
Log.e("ws------------musicUrl",musicUrl);
addBgMusci(videoUrl,musicUrl);
}
public native int addBgMusci(String videoUrl, String musicUrl);
最后调用ffmpeg API实现native 方法addBgMusci。
JNI源码如下:
#include <jni.h>
#include <string>
extern "C"
{
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <android/log.h>
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, "(^_^)", format, ##__VA_ARGS__)
//Output FFmpeg's av_log()
void custom_log(void *ptr, int level, const char* fmt, va_list vl){
FILE *fp=fopen("/storage/emulated/0/av_log.txt","a+");
if(fp){
vfprintf(fp,fmt,vl);
fflush(fp);
fclose(fp);
}
}
JNIEXPORT jint JNICALL
Java_com_ws_ffmpegandroidaddmusic_MainActivity_addBgMusci(
JNIEnv *env,
jobject , jstring input_video, jstring input_music) {
AVOutputFormat *ofmt = NULL;
//Input AVFormatContext and Output AVFormatContext
AVFormatContext *ifmt_ctx_v = NULL, *ifmt_ctx_a = NULL,*ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;
int videoindex_v=-1,videoindex_out=-1;
int audioindex_a=-1,audioindex_out=-1;
int frame_index=0;
int64_t cur_pts_v=0,cur_pts_a=0;
const char *in_filename_v = env->GetStringUTFChars(input_video,NULL);
const char *in_filename_a = env->GetStringUTFChars(input_music,NULL);
const char *out_filename = "/storage/emulated/0/wsAddMusic.flv";//Output file URL
av_register_all();
//Input
if ((ret = avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0)) < 0) {//打开输入的视频文件
LOGE( "Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx_v, 0)) < 0) {//获取视频文件信息
LOGE( "Failed to retrieve input stream information");
goto end;
}
if ((ret = avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0)) < 0) {//打开输入的音频文件
LOGE( "Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx_a, 0)) < 0) {//获取音频文件信息
LOGE( "Failed to retrieve input stream information");
goto end;
}
LOGE("===========Input Information==========\n");
av_dump_format(ifmt_ctx_v, 0, in_filename_v, 0);
av_dump_format(ifmt_ctx_a, 0, in_filename_a, 0);
LOGE("======================================\n");
//Output
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);//初始化输出码流的AVFormatContext。
if (!ofmt_ctx) {
LOGE( "Could not create output context\n");
ret = AVERROR_UNKNOWN;
return -1;
}
ofmt = ofmt_ctx->oformat;
//从输入的AVStream中获取一个输出的out_stream
for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {
//Create output AVStream according to input AVStream
if(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
AVStream *in_stream = ifmt_ctx_v->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);//创建流通道AVStream
videoindex_v=i;
if (!out_stream) {
LOGE( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
break;
}
videoindex_out=out_stream->index;
//Copy the settings of AVCodecContext
if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
LOGE( "Failed to copy context from input to output stream codec context\n");
break;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
break;
}
}
for (i = 0; i < ifmt_ctx_a->nb_streams; i++) {
//Create output AVStream according to input AVStream
if(ifmt_ctx_a->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
AVStream *in_stream = ifmt_ctx_a->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
audioindex_a=i;
if (!out_stream) {
LOGE( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
audioindex_out=out_stream->index;
//Copy the settings of AVCodecContext
if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
LOGE( "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
break;
}
}
LOGE("==========Output Information==========\n");
av_dump_format(ofmt_ctx, 0, out_filename, 1);
LOGE("======================================\n");
//Open output file
if (!(ofmt->flags & AVFMT_NOFILE)) {
if (avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {//打开输出文件。
LOGE( "Could not open output file '%s'", out_filename);
return -1;
}
}
//Write file header
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
LOGE( "Error occurred when opening output file\n");
return -1;
}
//FIX
#if USE_H264BSF
AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif
#if USE_AACBSF
AVBitStreamFilterContext* aacbsfc = av_bitstream_filter_init("aac_adtstoasc");
#endif
while (1) {
AVFormatContext *ifmt_ctx;
int stream_index=0;
AVStream *in_stream, *out_stream;
//Get an AVPacket . av_compare_ts是比较时间戳用的。通过该函数可以决定该写入视频还是音频。
if(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0){
ifmt_ctx=ifmt_ctx_v;
stream_index=videoindex_out;
if(av_read_frame(ifmt_ctx, &pkt) >= 0){
do{
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[stream_index];
if(pkt.stream_index==videoindex_v){
//FIX:No PTS (Example: Raw H.264) H.264裸流没有PTS,因此必须手动写入PTS
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
frame_index++;
}
cur_pts_v=pkt.pts;
break;
}
}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
}else{
break;
}
}else{
ifmt_ctx=ifmt_ctx_a;
stream_index=audioindex_out;
if(av_read_frame(ifmt_ctx, &pkt) >= 0){
do{
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[stream_index];
if(pkt.stream_index==audioindex_a){
//FIX:No PTS
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
frame_index++;
}
cur_pts_a=pkt.pts;
break;
}
}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
}else{
break;
}
}
//FIX:Bitstream Filter
#if USE_H264BSF
av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
#if USE_AACBSF
av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
//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;
pkt.stream_index=stream_index;
LOGE("Write 1 Packet. size:%5d\tpts:%lld\n",pkt.size,pkt.pts);
//Write AVPacket 音频或视频裸流
if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {
LOGE( "Error muxing packet\n");
break;
}
av_free_packet(&pkt);
}
//Write file trailer
av_write_trailer(ofmt_ctx);
#if USE_H264BSF
av_bitstream_filter_close(h264bsfc);
#endif
#if USE_AACBSF
av_bitstream_filter_close(aacbsfc);
#endif
end:
avformat_close_input(&ifmt_ctx_v);
avformat_close_input(&ifmt_ctx_a);
/* 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) {
LOGE( "Error occurred.\n");
return -1;
}
return 0;
}
}
如果不了解ffmpeg API的客官,可先看这里的系列文章:
http://blog.csdn.net/king1425/article/details/70598827
作者:King1425 发表于2017/5/22 17:59:14 原文链接
阅读:27 评论:0 查看评论