截取很简单,只要用mediacodec进行解码解出pcm格式的数据,再把pcm数据用mediacodec进行编码或者用其他第三方的进行编码 。

拼接就比较麻烦,音频的音质会受到采样率,比特率和声道的影响,所以理想的状态是这三个属性要一样进行拼接才能保证音质 。

举个栗子,a和b是两首采样率,比特率和声道都不一样的歌,要拼接成c,首先要设置c的采样率,比特率和声道,这里用a的来进行设置,然后拼接,播放c的时候会发现a部分的音质是没问题的,到了b部分的时候音质就会出现问题。

解决这个问题很简单,先把a和b的采样率,比特率和声道都转成一样就可以了。对于音视频开发的人来说这个问题很好解决,就写个转换采样率,比特率和声道的工具,或者使用 ffmpeg。

通过github找到了几个,经过测试最后选择了lamemp3,lamemp3是c语言写的,怎么编译网上很多就不说了,好了开始正题 。

首先说说思路,先通过mediacodec把要处理的几个音频解码出pcm文件,再把这些pcm文件通过lamemp3转成采样率,比特率和声道一样的mp3,再通过mediacodec把这些mp3合并成一个pcm数据,最后就是把这个pcm数据转成自己想要的格式,可以用mediacodec转成aac或者用lamemp3再转成mp3。

audioholder.java属性类

记录音频的采样率,比特率,声道,截取的开始时间,截取的结束时间,路径和文件名

public class audioholder {
    private string file;
    private string name;
    private double start;
    private double end;

    private int samplerate;
    private int channelcount;
    private int bitrate;

    private string mp3;

    public void setmp3(string mp3) {
        this.mp3 = mp3;
    }

    public string getmp3() {
        return mp3;
    }

    public string getfile() {
        return file;
    }

    public void setfile(string file) {
        this.file = file;
    }

    public string getname() {
        return name;
    }

    public void setname(string name) {
        this.name = name;
    }

    public double getstart() {
        return start;
    }

    public void setstart(double start) {
        this.start = start;
    }

    public double getend() {
        return end;
    }

    public void setend(double end) {
        this.end = end;
    }

    public int getsamplerate() {
        return samplerate;
    }

    public void setsamplerate(int samplerate) {
        this.samplerate = samplerate;
    }

    public int getchannelcount() {
        return channelcount;
    }

    public void setchannelcount(int channelcount) {
        this.channelcount = channelcount;
    }

    public int getbitrate() {
        return bitrate;
    }

    public void setbitrate(int bitrate) {
        this.bitrate = bitrate;
    }
}

simplelame.java调用lamemp3类

public class simplelame {
    static {
        system.loadlibrary("native-lib");
    }
    /**
     * pcm文件转换mp3函数
     */
    public static native void convert(audioencoder encoder,string jwav, string jmp3,
                                      int insamplerate, int outchannel, int outsamplerate, int outbitrate,
                                      int quality);
}

native-lib.cpp

#include <jni.h>
#include <string>
#include "lamemp3/lame.h"
#include <sys/stat.h>

#define inbufsize 4096
#define mp3bufsize (int) (1.25 * inbufsize) + 7200

extern "c"
jniexport void jnicall
java_com_hyq_hm_audiomerge_lame_simplelame_convert(jnienv *env, jclass type, jobject encoder,
                                                   jstring jwav_,jstring jmp3_,
                                                   jint insamplerate,jint outchannel,
                                                   jint outsamplerate,jint outbitrate,
                                                   jint quality) {
    const char *jwav = env->getstringutfchars(jwav_, 0);
    const char *jmp3 = env->getstringutfchars(jmp3_, 0);
    // todo
    short int wav_buffer[inbufsize*outchannel];
    unsigned char mp3_buffer[mp3bufsize];
//    获取文件大小
    struct stat st;
    stat(jwav, &st );
    jclass cls = env->getobjectclass(encoder);
    jmethodid mid = env->getmethodid(cls, "setprogress", "(jj)v");

    file* fwav = fopen(jwav,"rb");
    file* fmp3 = fopen(jmp3,"wb");
    lame_t lameconvert =  lame_init();

    lame_set_in_samplerate(lameconvert , insamplerate);
    lame_set_out_samplerate(lameconvert, outsamplerate);
    lame_set_num_channels(lameconvert,outchannel);
//    lame_set_vbr(lameconvert,vbr_mtrh);
//    lame_set_vbr_mean_bitrate_kbps(lameconvert,outbitrate);
    lame_set_brate(lameconvert,outbitrate);
    lame_set_quality(lameconvert, quality);
    lame_init_params(lameconvert);

    int read ; int write;
    long total=0;
    do{
        read = (int) fread(wav_buffer, sizeof(short int) * outchannel, inbufsize, fwav);
        total +=  read* sizeof(short int)*outchannel;
        env->callvoidmethod(encoder,mid,(long)st.st_size,total);
        if(read!=0){
            if (outchannel == 2){
                write = lame_encode_buffer_interleaved(lameconvert,wav_buffer,read,mp3_buffer,mp3bufsize);
            }else{
                write = lame_encode_buffer(lameconvert,wav_buffer,wav_buffer,read,mp3_buffer,mp3bufsize);
            }
        } else{
            write = lame_encode_flush(lameconvert,mp3_buffer,mp3bufsize);
        }
        fwrite(mp3_buffer, sizeof(unsigned char), (size_t) write, fmp3);
    }while (read!=0);
    lame_mp3_tags_fid(lameconvert,fmp3);
    lame_close(lameconvert);
    fclose(fwav);
    fclose(fmp3);

    env->releasestringutfchars(jwav_, jwav);
    env->releasestringutfchars(jmp3_, jmp3);
}

audiomerge.java拼接操作类

public class audiomerge {
private static final string audio = "audio/";
private handler audiohandler;
private handlerthread audiothread;
public audiomerge(){
audiothread = new handlerthread("audiomerge");
audiothread.start();
audiohandler = new handler(audiothread.getlooper());
}
private onaudioencoderlistener encoderlistener;
public void setencoderlistener(onaudioencoderlistener encoderlistener) {
this.encoderlistener = encoderlistener;
}
public void start(final string path, final list<audioholder> list){
audiohandler.post(new runnable() {
@override
public void run() {
encoders(path,list);
}
});
}
public void start(final string path, final list<audioholder> list,onaudioencoderlistener encoderlistener){
this.encoderlistener = encoderlistener;
start(path,list);
}
private static int[] samplerates = {48000,44100,32000,24000,22050,16000,12000,11025,8000};
private static int[] mpeg1bitrates = {320,256,224,192,160,128,112,96,80,64,56,48,40,32};
private static int[] mpeg2bitrates = {160,144,128,112,96,80,64,56,48,40,32,24,16,8};
private static int[] mpeg25bitrates = {64,56,48,40,32,24,16,8};
private int audiotrackindex;
private audioholder decoderholder = null;
/**
* 进行解码和拼接
*/
private void encoders(string path,list<audioholder> list){
file file = new file(path);
if(file.exists()){
file.delete();
}
//统一采样率,比特率和声道
int bitrate = list.get(0).getbitrate();
int samplerate = list.get(0).getsamplerate();
int channelcount = list.get(0).getchannelcount();
if(list.size() != 1){
for (audioholder holder:list){
bitrate = math.min(bitrate,holder.getbitrate());
samplerate = math.min(samplerate,holder.getsamplerate());
channelcount = math.min(channelcount,holder.getchannelcount());
}
samplerate = format(samplerate,samplerates);
if(samplerate >= samplerates[2]){
bitrate = format(bitrate,mpeg1bitrates);
}else if(samplerate <= samplerates[6]){
bitrate = format(bitrate,mpeg25bitrates);
}else{
bitrate = format(bitrate,mpeg2bitrates);
}
}
//临时用的pcm文件
string pcm = environment.getexternalstoragedirectory().getabsolutepath()+"/hmsdk/"+system.currenttimemillis()+".pcm";
list<string> mp3s = new arraylist<>();
//总时长,用来计算进度用的
long duration = 0;
for (audioholder holder :list){
//只有1个音频的时候直接转mp3
string mp3;
if(list.size() == 1){
mp3 = path;
decoderholder = null;
}else{
decoderholder = holder;
mp3 = environment.getexternalstoragedirectory().getabsolutepath()+"/hmsdk/"+system.currenttimemillis()+".mp3";
}
//将音频解码成pcm文件
duration += decoderpcm(holder,pcm);
//把pcm文件转成mp3
simplelame.convert(this,pcm,mp3
,holder.getsamplerate(),
channelcount,samplerate,bitrate,
1
);
mp3s.add(mp3);
}
//只有一个音频就完成操作
if(list.size() == 1){
if(encoderlistener != null){
encoderlistener.onover(path);
}
return;
}
//以下可换成其他代码,比如用mediacodec转成aac,因为采样率,比特率和声道都是一样的文件
decoderholder = null;
file f = new file(pcm);
if(f.exists()){
f.delete();
}
outputstream pcmos = null;
try {
pcmos = new fileoutputstream(pcm);
} catch (filenotfoundexception e) {
e.printstacktrace();
}
//文件总大小
long total = 0;
for (string mp3 : mp3s){
//将mp3转成pcm文件返回转换数据的大小
total += encodermp3(mp3,pcmos,total,duration);
}
try {
pcmos.flush();
pcmos.close();
} catch (ioexception e) {
e.printstacktrace();
}
//把pcm文件转成mp3
simplelame.convert(this,pcm,path
,samplerate,
channelcount,samplerate,bitrate,
1
);
if(encoderlistener != null){
encoderlistener.onover(path);
}
}
/**
* 进行解码
*/
private long decoderpcm(audioholder holder,string pcm){
long starttime = (long) (holder.getstart()*1000*1000);
long endtime = (long) (holder.getend()*1000*1000);
//初始化mediaextractor和mediacodec
mediaextractor audioextractor = new mediaextractor();
mediacodec audiodecoder = null;
try {
audioextractor.setdatasource(holder.getfile());
for (int i = 0; i < audioextractor.gettrackcount(); i++) {
mediaformat format = audioextractor.gettrackformat(i);
string mime = format.getstring(mediaformat.key_mime);
if(mime.startswith(audio)){
audioextractor.selecttrack(i);
audiotrackindex = i;
if(starttime != 0){
audioextractor.seekto(starttime,audiotrackindex);
}
audiodecoder = mediacodec.createdecoderbytype(mime);
audiodecoder.configure(format, null, null, 0);
audiodecoder.start();
break;
}
}
} catch (ioexception e) {
e.printstacktrace();
}
file f = new file(pcm);
if(f.exists()){
f.delete();
}
//pcm文件
outputstream pcmos = null;
try {
pcmos = new fileoutputstream(f);
} catch (filenotfoundexception e) {
e.printstacktrace();
}
//这段音频的时长
long duration = endtime - starttime;
mediacodec.bufferinfo info = new mediacodec.bufferinfo();
while (true) {
extractorinputbuffer(audioextractor, audiodecoder);
int outindex = audiodecoder.dequeueoutputbuffer(info, 50000);
if (outindex >= 0) {
bytebuffer data = audiodecoder.getoutputbuffer(outindex);
if ((info.flags & mediacodec.buffer_flag_codec_config) != 0) {
info.size = 0;
}
if (info.size != 0) {
//判断解码出来的数据是否在截取的范围内
if(info.presentationtimeus >= starttime && info.presentationtimeus <= endtime){
byte[] bytes = new byte[data.remaining()];
data.get(bytes,0,bytes.length);
data.clear();
//写入pcm文件
try {
pcmos.write(bytes);
} catch (ioexception e) {
e.printstacktrace();
}
//进度条
if(encoderlistener != null){
int progress = (int) (((info.presentationtimeus - starttime)*50)/duration);
if(decoderholder == null){
encoderlistener.onencoder(progress);
}else{
encoderlistener.ondecoder(decoderholder,progress);
}
}
}
}
audiodecoder.releaseoutputbuffer(outindex, false);
//超过截取时间结束解码
if(info.presentationtimeus >= endtime){
break;
}
}
if ((info.flags & mediacodec.buffer_flag_end_of_stream) != 0) {
break;
}
}
try {
pcmos.flush();
pcmos.close();
} catch (ioexception e) {
e.printstacktrace();
}
audiodecoder.stop();
audiodecoder.release();
audioextractor.release();
return duration;
}
/**
* mp3转pcm
*/
private long encodermp3(string mp3,outputstream pcmos,long starttime,long duration){
long d = 0;
mediaextractor audioextractor = new mediaextractor();
mediacodec audiodecoder = null;
try {
audioextractor.setdatasource(mp3);
for (int i = 0; i < audioextractor.gettrackcount(); i++) {
mediaformat format = audioextractor.gettrackformat(i);
string mime = format.getstring(mediaformat.key_mime);
if(mime.startswith(audio)){
d = format.getlong(mediaformat.key_duration);
audioextractor.selecttrack(i);
audiodecoder = mediacodec.createdecoderbytype(mime);
audiodecoder.configure(format, null, null, 0);
audiodecoder.start();
break;
}
}
} catch (ioexception e) {
e.printstacktrace();
}
mediacodec.bufferinfo info = new mediacodec.bufferinfo();
while (true) {
extractorinputbuffer(audioextractor, audiodecoder);
int outindex = audiodecoder.dequeueoutputbuffer(info, 50000);
if (outindex >= 0) {
bytebuffer data = audiodecoder.getoutputbuffer(outindex);
if ((info.flags & mediacodec.buffer_flag_codec_config) != 0) {
info.size = 0;
}
if (info.size != 0) {
byte[] bytes = new byte[data.remaining()];
data.get(bytes,0,bytes.length);
data.clear();
try {
pcmos.write(bytes);
} catch (ioexception e) {
e.printstacktrace();
}
if(encoderlistener != null){
int progress = (int) (((info.presentationtimeus + starttime)*50)/duration);
encoderlistener.onencoder(progress);
}
}
audiodecoder.releaseoutputbuffer(outindex, false);
}
if ((info.flags & mediacodec.buffer_flag_end_of_stream) != 0) {
break;
}
}
audiodecoder.stop();
audiodecoder.release();
audioextractor.release();
return d;
}
private void extractorinputbuffer(mediaextractor mediaextractor, mediacodec mediacodec) {
int inputindex = mediacodec.dequeueinputbuffer(50000);
if (inputindex >= 0) {
bytebuffer inputbuffer = mediacodec.getinputbuffer(inputindex);
long sampletime = mediaextractor.getsampletime();
int samplesize = mediaextractor.readsampledata(inputbuffer, 0);
if (mediaextractor.advance()) {
mediacodec.queueinputbuffer(inputindex, 0, samplesize, sampletime, 0);
} else {
if (samplesize > 0) {
mediacodec.queueinputbuffer(inputindex, 0, samplesize, sampletime, mediacodec.buffer_flag_end_of_stream);
} else {
mediacodec.queueinputbuffer(inputindex, 0, 0, 0, mediacodec.buffer_flag_end_of_stream);
}
}
}
}
private int format(int f,int[] fs){
if(f >= fs[0]){
return fs[0];
}else if(f <= fs[fs.length - 1]){
return fs[fs.length - 1];
}else{
for (int i = 1; i < fs.length;i++){
if(f >= fs[i]){
return fs[i];
}
}
}
return -1;
}
/**
* jni回调的进度条函数,进度条以解码占50,pcm转mp3占50
*/
public void setprogress(long size,long total){
if(encoderlistener != null){
int progress = 50 + (int) ((total*50)/size);
if(decoderholder == null){
encoderlistener.onencoder(progress);
}else{
encoderlistener.ondecoder(decoderholder,progress);
}
}
}
public interface onaudioencoderlistener{
void ondecoder(audioholder decoderholder,int progress);
void onencoder(int progress);
void onover(string path);
}
} 

使用

private audiomerge audiomerge = new audiomerge();
private list<audioholder> list = new arraylist<>();
audiomerge.start(environment.getexternalstoragedirectory().getabsolutepath()+"/hmsdk/test_merge.mp3",list);

还有hmsdk这个文件夹自己创建或改成自己的,我都是保存在手机内是为了方便测试

github: https://github.com/a422070876/audiomerge

以上就是android开发mediacodec和lamemp3多段音频截取拼接的详细内容,更多关于mediacodec和lamemp3音频截取拼接的资料请关注其它相关文章!