第一种情况:不同压缩格式音频拼接,不同的压缩格式拼接需要解码为采样数据然后拼接,然后再编码为统一的压缩格式。

方法一:FFmpeg命令拼接,ffmpeg -I ‘concat:0.mp3|1.wav|2.aac’ -acodec copy merge.mp3。(注意:这种方式,速度相对还可以,但是在android设备上一下子拼接6个音频以上就会奔溃,应该是C代码中有什么变量没有释放掉)

  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|%sconcatAudioCmd = 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) {//解码一帧音频压缩数据,得到一帧音频采样数据//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() {@Overridepublic 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() {@Overridepublic void run() { try {for (String audioPath : audioList) {//拼接jointAudio(audioPath, path + "eng100100.mp3");}catch (Exception ex){ex.printStackTrace();}}}).start();}

本文章著作版权所属:微笑面对,请关注我的CSDN博客:博客地址

android开发之音频拼接相关推荐

  1. Android 开发 AudioRecord音频录制

    前言 Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压 ...

  2. android 字符串拼接 drawable文件,【Android】android开发之文字拼接图片,图文混排...

    前言 需求:给一个字符串拼接上一个"全国"标签,需要一直跟在文字后面. ui图: 想法: 采用spannable的方法给文字后面添加图片. 正文 1.写一个xml当"全国 ...

  3. Android开发记录:视频提取音频

    文章目录 前言 使用方法 前言 Android开发中从视频中提取音频主要有三种方式结合FFmpeg,MP4Parser,MediaExtractor. 多媒体视频处理工具FFmpeg有非常强大的功能包 ...

  4. Android旋转视频工具类,Android开发实现的IntentUtil跳转多功能工具类【包含视频、音频、图片、摄像头等操作功能】...

    本文实例讲述了Android开发实现的IntentUtil跳转多功能工具类.分享给大家供大家参考,具体如下: 说明:此工具类是本人开发中总结下来的,还有其它的跳转亲给我留言,希望大家一起把这个工具类打 ...

  5. Android音视频开发之音频录制和播放

    Android音视频开发之音频录制和播放 1.封装音频录制工具类: public class RecorderAudioManagerUtils {private static volatile Re ...

  6. 【Oboe——Android低延迟音频应用开发库使用介绍】

    Oboe--Android低延迟音频应用开发库使用介绍 一. 背景 Oboe是一个C++库,是Google于2018年开发用来为Android打造高性能的互动音频体验,可在99%的安卓设备上实现最低可 ...

  7. JavaCV开发详解之29:使用javacv将多个视频拼接合成单个视频,多个音频拼接合成单个音频,以多个mp4视频合成一个mp4为例

    javacv实战专栏目录: JavaCV实战专栏文章目录(JavaCV速查手册) 前言 上一章中我们讲了多张图片合成视频,本章将再此基础上继续拓展,将多个视频/音频拼接合成一个视频/音频文件. 参考资 ...

  8. Android 开发中原始音频的录播和和自定义音频控制条的讲解及实战(超详细 附源码)

    需要源码请点赞关注收藏后评论区留下QQ~~~ 一.原始音频的录播 语音通话功能要求实时传输,如果使用MediaRecorder与MediaPlayer组合,那么只能整句话都录完并编码好了才能传给对方去 ...

  9. Android开发笔记(一百一十)使用http框架上传文件

    HTTP上传 与文件下载相比,文件上传的场合不是很多,通常用于上传用户头像.朋友圈发布图片/视频动态等等,而且上传文件需要服务器配合,所以容易被app开发者忽略.就上传的形式来说,app一般采用htt ...

最新文章

  1. 6位图灵奖获得者、10多位院士在线“教学”,这套关于“人工智能下一个十年”的课程免费提供给你...
  2. 使用caffe训练时Loss变为nan的原因
  3. R语言观察日志(part7)--RMarkdwon之代码块
  4. 线程类C++多线程框架(一)--------- new一下就启动一个线程
  5. double取值范围 java_Java中float/double取值范围与精度
  6. 华为手机安卓系统可能停更,鸿蒙系统手机要来了
  7. OpenSSL:openssl-xxx.tar.gz
  8. 需求跟踪矩阵(Requirement Tracking Matrix)
  9. java获取本地真实ip
  10. 五月北京清凉自驾游好去处
  11. matlab两条曲线方程求交点_帮忙matlab求两条曲线交点程序,不知问题出在哪里。...
  12. 程序员在二线城市工作爽吗?
  13. 手游传奇刷元宝_战神传奇手游刷元宝方法技巧大全
  14. USTC English Club Note20211227
  15. 小程序推广的6个超简单方案
  16. 〖EXP〗Ladon打印机漏洞提权CVE-2021-1675复现
  17. 堡垒机如何传输文件_mac 堡垒机传文件
  18. 数据库创建之主文件不能容纳副本的解决方案
  19. cvc-complex-type.2.4.d: 发现了以元素 ‘base-extension‘ 开头的无效内容。此处不应含有子元素。
  20. mt管理器显示连接服务器失败,mt管理器连接ftp服务器

热门文章

  1. PHP常用字符串处理函数
  2. python django 商品进销存管理系统
  3. idea中输入中文变成繁体字
  4. YT8511芯片手册 解析|CSDN创作打卡
  5. 小程序投标书_快来学习招投标小技巧!中标率提高50%(建议收藏)
  6. 经纬度计算距离的公式
  7. TMS320F2812调试记录一
  8. 基于JAVA校园社团活动管理系统计算机毕业设计源码+系统+数据库+lw文档+部署
  9. SNN系列|神经元模型篇(3)SRM
  10. 4. iconfont 字体图标无法正常渲染显示