android开发之音频拼接
2018-02-26 23:38
393 查看
第一种情况:不同压缩格式音频拼接,不同的压缩格式拼接需要解码为采样数据然后拼接,然后再编码为统一的压缩格式。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202012/09/b120361f328e317584e22428c6671162)
方法一:FFmpeg命令拼接,ffmpeg -I ‘concat:0.mp3|1.wav|2.aac’ -acodec copy merge.mp3。
方法二:FFmpeg解码为采样数据之后拼接采样数据,然后再编码为压缩格式数据。(这里我选用了FFmpeg进行编解码,当然也可以选择Android系统提供的MediaCodec进行解码拼接再编码)
第二种情况,相同格式音频拼接,只需要字节流拼接即可,当然如果不嫌效率低也可以选用以上两种方式进行拼接。
本文章著作版权所属:微笑面对,请关注我的CSDN博客:博客地址
方法一:FFmpeg命令拼接,ffmpeg -I ‘concat:0.mp3|1.wav|2.aac’ -acodec copy merge.mp3。
static { System.loadLibrary("MyLib"); } public native void command(int len,String[] argv); /** * 使用ffmpeg命令行进行音频合并 * @param src 源文件 * @param targetFile 目标文件 * @return 合并后的文件 */ public static String[] concatAudio(String[] src, String targetFile){ String join = StringUtils.join("|", src); String concatAudioCmd = "ffmpeg -i concat:%s -acodec copy %s";//%s|%s concatAudioCmd = String.format(concatAudioCmd, join, targetFile); return concatAudioCmd.split(" ");//以空格分割为字符串数组 } /** * 拼接音频 * @param paths 音频地址集合 * @return 音频拼接之后的地址 */ private String jointAudio1(List<String> paths) { String path = ""; for (int i = 1; i < paths.size(); i++) { String[] pathArr = new String[2]; if (i==1) { pathArr[0] = paths.get(i - 1); pathArr[1] = paths.get(i); }else{ pathArr[0] = path; pathArr[1] = paths.get(i); } File file = new File(paths.get(0)); path = file.getParent().concat(File.separator).concat(String.valueOf(System.currentTimeMillis()).concat("-debris.mp3")); String[] command = FFmpegUtil.concatAudio(pathArr, path); command(command.length,command); } return path; }
#include <jni.h> #include <malloc.h> #include <string.h> #include "ffmpeg.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include <libavutil/imgutils.h> #include <libswscale/swscale.h> //音频采样 #include <libswresample/swresample.h> #include <android/log.h> #define LOG_I_ARGS(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"main",FORMAT,__VA_ARGS__); #define LOG_I(FORMAT) LOG_I_ARGS(FORMAT,0); //视频转码压缩主函数入口 //ffmpeg_mod.c有一个FFmpeg视频转码主函数入口 // argc = str.split(" ").length() // argv = str.split(" ") 字符串数组 //参数一:命令行字符串命令个数 //参数二:命令行字符串数组 int ffmpegmain(int argc, char **argv); JNIEXPORT void JNICALL Java_com_xy_openndk_audiojointdemo_FFmpegLib_command (JNIEnv *env, jobject jobj,jint jlen,jobjectArray jobjArray){ //转码 //将java的字符串数组转成C字符串 int argc = jlen; //开辟内存空间 char **argv = (char**)malloc(sizeof(char*) * argc); //填充内容 for (int i = 0; i < argc; ++i) { jstring str = (*env)->GetObjectArrayElement(env,jobjArray,i); const char* tem = (*env)->GetStringUTFChars(env,str,0); argv[i] = (char*)malloc(sizeof(char)*1024); strcpy(argv[i],tem); (*env)->ReleaseStringUTFChars(env,str,tem); } //开始转码(底层实现就是只需命令) ffmpegmain(argc,argv); //释放内存空间 for (int i = 0; i < argc; ++i) { free(argv[i]); } //释放数组 free(argv); }
方法二:FFmpeg解码为采样数据之后拼接采样数据,然后再编码为压缩格式数据。(这里我选用了FFmpeg进行编解码,当然也可以选择Android系统提供的MediaCodec进行解码拼接再编码)
include <jni.h> #include <android/log.h> extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/imgutils.h" #include "libswscale/swscale.h" //音频采样 #include "libswresample/swresample.h" #include "mp3enc/lame.h" } #define LOG_I_ARGS(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"main",FORMAT,__VA_ARGS__); #define LOG_I(FORMAT) LOG_I_ARGS(FORMAT,0); #define MAX_AUDIO_FRAME_SIZE (44100) AVFormatContext *av_fm_ctx = NULL; AVCodecParameters *av_codec_pm = NULL; AVCodec *av_codec = NULL; AVCodecContext *av_codec_ctx = NULL; AVPacket *packet = NULL; AVFrame *in_frame = NULL; SwrContext *swr_ctx = NULL; uint8_t *out_buffer = NULL; /** * 音频解码 * @param out 拼接的采样数据文件 * @param path 音频地址 */ void decodeAudio(FILE *out, const char *path); /** * 音频编码 * @param path PCM文件地址 * @param out 输出文件地址 */ void encoder(const char* path,const char* out); extern "C" JNIEXPORT void JNICALL Java_com_xy_audio_ffmpegjointaudio_MainActivity_jointAudio(JNIEnv *env, jobject instance, jobjectArray paths_, jstring path_,jstring other_) { jsize len = env->GetArrayLength(paths_); //音频输入文件 const char *out = env->GetStringUTFChars(path_, NULL); const char* other = env->GetStringUTFChars(other_,NULL); // //写入文件 FILE *file_out_dcm = fopen(out, "wb+"); //注册输入输出组件 av_register_all(); for (int i = 0; i < len; i++) { jstring str = (jstring) env->GetObjectArrayElement(paths_, i); const char *path = env->GetStringUTFChars(str, 0); LOG_I(path); //解码拼接 decodeAudio(file_out_dcm, path); env->ReleaseStringUTFChars(str, path); } fclose(file_out_dcm); env->ReleaseStringUTFChars(path_, out); env->ReleaseStringUTFChars(other_,other); av_packet_free(&packet); if(out_buffer != NULL) av_freep(out_buffer); avformat_close_input(&av_fm_ctx); avformat_free_context(av_fm_ctx); //编码 encoder(out,other); } /** * 音频解码 * @param out 输出文件 * @param path 解码的文件地址 */ void decodeAudio(FILE *out, const char *path) { av_fm_ctx = avformat_alloc_context(); int av_fm_open_result = avformat_open_input(&av_fm_ctx, path, NULL, NULL); if (av_fm_open_result != 0) { LOG_I("打开失败!"); return; } //获取音频文件信息 if (avformat_find_stream_info(av_fm_ctx, NULL) < 0) { LOG_I("获取信息失败"); return; } //查找音频解码器 //找到音频流索引位置 int audio_stream_index = -1; for (int i = 0; i < av_fm_ctx->nb_streams; i++) { //查找音频流索引位置 if (av_fm_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream_index = i; break; } } //判断是否存在音频流 if (audio_stream_index == -1) { LOG_I("没有这个音频流!"); return; } //获取编码器上下文(获取编码器ID) av_codec_pm = av_fm_ctx->streams[audio_stream_index]->codecpar; //获取解码器(根据编码器的ID,找到对应的解码器) av_codec = avcodec_find_decoder(av_codec_pm->codec_id); //打开解码器 av_codec_ctx = avcodec_alloc_context3(av_codec); //根据所提供的编解码器的值填充编译码上下文 int avcodec_to_context = avcodec_parameters_to_context(av_codec_ctx,av_codec_pm); if(avcodec_to_context < 0){ return; } int av_codec_open_result = avcodec_open2(av_codec_ctx, av_codec, NULL); if (av_codec_open_result != 0) { LOG_I("解码器打开失败!"); return; } //从输入文件读取一帧压缩数据 //循环遍历 //保存一帧读取的压缩数据-(提供缓冲区) packet = (AVPacket *) av_malloc(sizeof(AVPacket)); //内存分配 in_frame = av_frame_alloc(); //定义上下文(开辟内存) swr_ctx = swr_alloc(); //设置音频采样上下文参数(例如:码率、采样率、采样格式、输出声道等等......) //swr_alloc_set_opts参数分析如下 //参数一:音频采样上下文 //参数二:输出声道布局(例如:立体、环绕等等......) //立体声 uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO; //参数三:输出音频采样格式(采样精度) AVSampleFormat av_sm_fm = AV_SAMPLE_FMT_S16; //参数四:输出音频采样率(例如:44100Hz、48000Hz等等......) //在这里需要注意:保证输出采样率和输入的采样率保证一直(如果你不想一直,你可进行采样率转换) int out_sample_rate = av_codec_ctx->sample_rate; //输入声道布局 int64_t in_ch_layout = av_get_default_channel_layout(av_codec_ctx->channels); //参数六:输入音频采样格式(采样精度) //参数七:输入音频采样率(例如:44100Hz、48000Hz等等......) //参数八:偏移量 //参数九:日志统计上下文 swr_alloc_set_opts(swr_ctx, out_ch_layout, av_sm_fm, out_sample_rate, in_ch_layout, av_codec_ctx->sample_fmt, av_codec_ctx->sample_rate, 0, NULL); //初始化音频采样数据上下文 swr_init(swr_ctx); //音频采样数据缓冲区(每一帧大小) //44100 16bit 大小: size = 44100 * 2 / 1024 = 86KB //最大采样率 out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRAME_SIZE); //获取输出声道数量(根据声道布局获取对应的声道数量) int out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout); //大于等于0,继续读取,小于0说明读取完毕或者读取失败 int ret, index = 0; while (av_read_frame(av_fm_ctx, packet) >= 0) { //解码一帧音频压缩数据得到音频采样数据 if (packet->stream_index == audio_stream_index) { //7.解码一帧音频压缩数据,得到一帧音频采样数据 //0:表示成功(成功解压一帧音频压缩数据) //AVERROR(EAGAIN): 现在输出数据不可用,可以尝试发送一帧新的视频压缩数据(或者说尝试解压下一帧视频压缩数据) //AVERROR_EOF:解码完成,没有新的视频压缩数据 //AVERROR(EINVAL):当前是一个编码器,但是编解码器未打开 //AVERROR(ENOMEM):解码一帧视频压缩数据发生异常 avcodec_send_packet(av_codec_ctx, packet); //返回值解释: //0:表示成功(成功获取一帧音频采样数据) //AVERROR(EAGAIN): 现在输出数据不可用,可以尝试接受一帧新的视频像素数据(或者说尝试获取下一帧视频像素数据) //AVERROR_EOF:接收完成,没有新的视频像素数据了 //AVERROR(EINVAL):当前是一个编码器,但是编解码器未打开 ret = avcodec_receive_frame(av_codec_ctx, in_frame); if (ret == 0) { //将音频采样数据保存(写入到文件中) //音频采样数据格式是:PCM格式、采样率(44100Hz)、16bit //对音频采样数据进行转换为PCM格式 //参数一:音频采样上下文 //参数二:输出音频采样缓冲区 //参数三:输出缓冲区大小 //参数四:输入音频采样数据 //参数五:输入音频采样数据大小 swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **) in_frame->data, in_frame->nb_samples); //获取缓冲区实际数据大小 //参数一:行大小 //参数二:输出声道个数 //参数三:输入的大小 //参数四:输出的音频采样数据格式 //参数五:字节对齐 int out_buffer_size = av_samples_get_buffer_size(NULL, out_nb_channels,in_frame->nb_samples,av_sm_fm, 1); //写入到文件中 fwrite(out_buffer, 1, (size_t) out_buffer_size, out); LOG_I_ARGS("音频帧:%d\n", ++index); } } } swr_close(swr_ctx); swr_free(&swr_ctx); av_frame_free(&in_frame); avcodec_parameters_free(&av_codec_pm); avcodec_close(av_codec_ctx); avcodec_free_context(&av_codec_ctx); } /** * 音频编码 * @param path PCM文件地址 * @param out 输出文件地址 */ void encoder(const char* path,const char* out){ //打开 pcm,MP3文件 FILE* fpcm = fopen(path,"rb"); FILE* fmp3 = fopen(out,"wb"); short int pcm_buffer[8192*2]; unsigned char mp3_buffer[8192]; //初始化lame的编码器 lame_t lame = lame_init(); //设置lame mp3编码的采样率 lame_set_in_samplerate(lame , 44100); lame_set_num_channels(lame,2); //设置MP3的编码方式 lame_set_VBR(lame, vbr_default); lame_init_params(lame); LOG_I("lame init finish"); int read ; int write; //代表读了多少个次 和写了多少次 int total=0; // 当前读的wav文件的byte数目 do{ read = fread(pcm_buffer,sizeof(short int)*2, 8192,fpcm); total += read* sizeof(short int)*2; LOG_I_ARGS("converting ....%d", total); // 调用java代码 完成进度条的更新 if(read!=0){ write = lame_encode_buffer_interleaved(lame,pcm_buffer,read,mp3_buffer,8192); //把转化后的mp3数据写到文件里 fwrite(mp3_buffer,sizeof(unsigned char),write,fmp3); } if(read==0){ lame_encode_flush(lame,mp3_buffer,8192); } }while(read!=0); LOG_I("convert finish"); lame_close(lame); fclose(fpcm); fclose(fmp3); }
static { System.loadLibrary("native-lib"); } /** * 拼接音频 * @param paths 音频地址集合 * @param path 采样数据地址 * @param out 编码数据地址 */ public native void jointAudio(String[]paths,String path,String out); public void jointAudioClick(View view) { List<String> audioList = new ArrayList<String>(); audioList.add(path+"0.mp3"); audioList.add(path+"1.wav"); audioList.add(path+"2.aac"); new Thread(new Runnable() { @Override public void run() { jointAudio(finalPaths,target,path+"eng100.mp3"); } }).start(); }
第二种情况,相同格式音频拼接,只需要字节流拼接即可,当然如果不嫌效率低也可以选用以上两种方式进行拼接。
public void jointAudio(String audioPath, String toPath)throws Exception { File audioFile = new File(audioPath); File toFile = new File(toPath); FileInputStream in=new FileInputStream(audioFile); FileOutputStream out=new FileOutputStream(toFile,true); byte bs[]=new byte[1024*4]; int len=0; //先读第一个 while((len=in.read(bs))!=-1){ out.write(bs,0,len); } in.close(); out.close(); } public void jointAudioClick(View view) { List<String> audioList = new ArrayList<String>(); audioList.add(path+"0.mp3"); audioList.add(path+"1.mp3"); audioList.add(path+"2.mp3"); new Thread(new Runnable() { @Override public void run() { try { for (String audioPath : audioList) { //拼接 jointAudio(audioPath, path + "eng100100.mp3"); }catch (Exception ex){ ex.printStackTrace(); } } }).start(); }
本文章著作版权所属:微笑面对,请关注我的CSDN博客:博客地址
相关文章推荐
- Android开发之控制手机音频
- android通过数组,流播放声音的方法,音频实时传输(安卓软件开发)
- Android应用开发学习笔记之播放音频
- Android开发中如何把多个JSONArray组合(拼接)在一起,组成一个整体的字符串。
- Android下音频录制以及网络传输的思路及开发方法
- 【Android游戏开发之八】游戏中添加音频-详解MediaPlayer与SoundPoo!并讲解两者的区别和游戏中的用途!
- Android音频开发之尝试音频混合
- Android音频开发(7):使用 OpenSL ES API(下)
- Android音频开发(4):如何存储和解析wav文件
- Android 音频开发-如何存储和解析wav文件
- Android音频开发过程中遇到的问题
- Android游戏开发系列教程第四讲(游戏音频)
- Android开发之MediaPlayer多媒体(音频,视频)播放工具类
- Android音频开发之AudioTrack实时播放
- android音频开发技术分析
- Android音频开发之使用AudioRecord录制
- Android 多媒体开发-音频
- Android开发 使用Lame把音频文件转换成mp3格式
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- 木瓜妮子多媒体开发教程---第六天---Android下的音频均衡器