欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

利用libmp3lame实现在Android上录音MP3文件示例

程序员文章站 2022-07-21 15:02:07
之前项目需要实现mp3的录音,于是使用上了lame这个库。这次做一个demo,使用androidstudio+cmake+ndk进行开发。利用android sdk提供的a...

之前项目需要实现mp3的录音,于是使用上了lame这个库。这次做一个demo,使用androidstudio+cmake+ndk进行开发。利用android sdk提供的androidrecorder进行录音,得到pcm数据,并使用jni调用lame这个c库将pcm数据转换为mp3文件。并使用mediaplayer对录音的mp3文件进行播放。另外此次的按键是仿微信的语音按键,按下录音,松开结束,若中途上滑松开即取消

效果如下:

利用libmp3lame实现在Android上录音MP3文件示例

项目地址: lamemp3forandroid_jb51.rar

一、主要类的介绍

  • mp3recorder—— 是负责调用audiorecorder进行录音的类
  • simplelame——是负责将mp3recorder录制出的pcm数据转换成mp3文件
  • dataencodethread——是负责执行pcm转mp3的线程
  • lamemp3manager——是对mp3recorder的多一次封装,增加了取消后删除之前录制的数据的逻辑
  • mediaplayerutil——是对系统的mediaplayer进行简单的封装,使其只需要三步就可以播放音频文件
  • mediarecorderbutton ——是一个仿微信录音按键的控件,按下录制,松开结束,录制时上滑则取消录制

二、录制的流程

  1. mp3recorder调用startrecording()开始录制并初始化dataencoderthread线程,并定期将录制的pcm数据,传入dataencoderthread中。
  2. 在dataencoderthread里,simplelame将mp3recorder传入的pcm数据转换成mp3格式并写入文件,其中simplelame通过jni对lame库进行调用
  3. mp3recorder调用stoprecording()停止录制,并通知dataencoderthread线程录制结束,dataencoderthread将剩余的数据转换完毕。

三、主要的实现代码

mp3recorder

public class mp3recorder {
  static {
    system.loadlibrary("lamemp3");
  }
  //默认采样率
  private static final int default_sampling_rate = 44100;
  //转换周期,录音每满160帧,进行一次转换
  private static final int frame_count = 160;
  //输出mp3的码率
  private static final int bit_rate = 32;
  //根据资料假定的最大值。 实测时有时超过此值。
  private static final int max_volume = 2000;
  private audiorecord audiorecord = null;
  private int buffersize;
  private file mp3file;
  private int mvolume;
  private short[] mpcmbuffer;
  private fileoutputstream os = null;
  private dataencodethread encodethread;
  private int samplingrate;
  private int channelconfig;
  private pcmformat audioformat;
  private boolean isrecording = false;
  private executorservice executor = executors.newfixedthreadpool(1);
  private onfinishlistener finishlistener;

  public interface onfinishlistener {
    void onfinish(string mp3savepath);
  }

  public mp3recorder(int samplingrate, int channelconfig, pcmformat audioformat) {
    this.samplingrate = samplingrate;
    this.channelconfig = channelconfig;
    this.audioformat = audioformat;
  }

  public mp3recorder() {
    this(default_sampling_rate, audioformat.channel_in_mono, pcmformat.pcm_16bit);
  }


  public void startrecording(file mp3save) throws ioexception {
    if (isrecording) return;
    this.mp3file = mp3save;
    if (audiorecord == null) {
      initaudiorecorder();
    }
    audiorecord.startrecording();
    runnable runnable = new runnable() {
      @override
      public void run() {
        isrecording = true;
        //循环的从audiorecord获取录音的pcm数据
        while (isrecording) {
          int readsize = audiorecord.read(mpcmbuffer, 0, buffersize);
          if (readsize > 0) {
            //待转换的pcm数据放到转换线程中
            encodethread.addchangebuffer(mpcmbuffer,readsize);
            calculaterealvolume(mpcmbuffer, readsize);
          }
        }
        // 录音完毕,释放audiorecord的资源
        try {
          audiorecord.stop();
          audiorecord.release();
          audiorecord = null;
          // 录音完毕,通知转换线程停止,并等待直到其转换完毕
          message msg = message.obtain(encodethread.gethandler(), dataencodethread.process_stop);
          msg.sendtotarget();
          encodethread.join();
          //转换完毕后回调监听
          if(finishlistener != null) finishlistener.onfinish(mp3file.getpath());
        } catch (interruptedexception e) {
          e.printstacktrace();
        } finally {
          if (os != null) {
            try {
              os.close();
            } catch (ioexception e) {
              e.printstacktrace();
            }
          }
        }
      }
    };
    executor.execute(runnable);
  }

  public void stoprecording() throws ioexception {
    isrecording = false;
  }

  //计算音量大小
  private void calculaterealvolume(short[] buffer, int readsize) {
    double sum = 0;
    for (int i = 0; i < readsize; i++) {
      sum += buffer[i] * buffer[i];
    }
    if (readsize > 0) {
      double amplitude = sum / readsize;
      mvolume = (int) math.sqrt(amplitude);
    }
  }

  public int getvolume(){
    if (mvolume >= max_volume) {
      return max_volume;
    }
    return mvolume;
  }

  public int getmaxvolume(){
    return max_volume;
  }

  public void setfinishlistener(onfinishlistener listener){
    this.finishlistener = listener;
  }

  private void initaudiorecorder() throws ioexception {
    int bytesperframe = audioformat.getbytesperframe();
    //计算缓冲区的大小,使其是设置周期帧数的整数倍,方便循环
    int framesize = audiorecord.getminbuffersize(samplingrate, channelconfig, audioformat.getaudioformat()) / bytesperframe;
    if (framesize % frame_count != 0) {
      framesize = framesize + (frame_count - framesize % frame_count);
    }
    buffersize = framesize * bytesperframe;

    audiorecord = new audiorecord(mediarecorder.audiosource.mic, samplingrate, channelconfig, audioformat.getaudioformat(), buffersize);
    mpcmbuffer = new short[buffersize];
    simplelame.init(samplingrate, 1, samplingrate, bit_rate);
    os = new fileoutputstream(mp3file);
    // 创建转码的线程
    encodethread = new dataencodethread(os, buffersize);
    encodethread.start();
    //给audiorecord设置刷新监听,待录音帧数每次达到frame_count,就通知转换线程转换一次数据
    audiorecord.setrecordpositionupdatelistener(encodethread, encodethread.gethandler());
    audiorecord.setpositionnotificationperiod(frame_count);
  }
}

dataencodethread

public class dataencodethread extends thread implements audiorecord.onrecordpositionupdatelistener {

  public static final int process_stop = 1;
  private stophandler handler;
  private byte[] mp3buffer;
  //用于存取待转换的pcm数据
  private list<changebuffer> mchangebuffers = collections.synchronizedlist(new linkedlist<changebuffer>());
  private fileoutputstream os;
  private countdownlatch handlerinitlatch = new countdownlatch(1);

  private static class stophandler extends handler {
    weakreference<dataencodethread> encodethread;

    public stophandler(dataencodethread encodethread) {
      this.encodethread = new weakreference<>(encodethread);
    }

    @override
    public void handlemessage(message msg) {
      if (msg.what == process_stop) {
        dataencodethread threadref = encodethread.get();
        //录音停止后,将剩余的pcm数据转换完毕
        for (;threadref.processdata() > 0;);
        removecallbacksandmessages(null);
        threadref.flushandrelease();
        getlooper().quit();
      }
      super.handlemessage(msg);
    }
  }

  public dataencodethread(fileoutputstream os, int buffersize) {
    this.os = os;
    mp3buffer = new byte[(int) (7200 + (buffersize * 2 * 1.25))];
  }

  @override
  public void run() {
    looper.prepare();
    handler = new stophandler(this);
    handlerinitlatch.countdown();
    looper.loop();
  }

  public handler gethandler() {
    try {
      handlerinitlatch.await();
    } catch (interruptedexception e) {
      e.printstacktrace();
      log.e(tag, "error when waiting handle to init");
    }
    return handler;
  }

  @override
  public void onmarkerreached(audiorecord recorder) {
    // do nothing
  }

  @override
  public void onperiodicnotification(audiorecord recorder) {
    //由audiorecord进行回调,满足帧数,通知数据转换
    processdata();
  }

  //从缓存区changebuffers里获取待转换的pcm数据,转换为mp3数据,并写入文件
  private int processdata() {
    if(mchangebuffers != null && mchangebuffers.size() > 0) {
      changebuffer changebuffer = mchangebuffers.remove(0);
      short[] buffer = changebuffer.getdata();
      int readsize = changebuffer.getreadsize();
      log.d(tag, "read size: " + readsize);
      if (readsize > 0) {
        int encodedsize = simplelame.encode(buffer, buffer, readsize, mp3buffer);
        if (encodedsize < 0) {
          log.e(tag, "lame encoded size: " + encodedsize);
        }
        try {
          os.write(mp3buffer, 0, encodedsize);
        } catch (ioexception e) {
          e.printstacktrace();
          log.e(tag, "unable to write to file");
        }
        return readsize;
      }
    }
    return 0;
  }

  private void flushandrelease() {
    final int flushresult = simplelame.flush(mp3buffer);

    if (flushresult > 0) {
      try {
        os.write(mp3buffer, 0, flushresult);
      } catch (final ioexception e) {
        e.printstacktrace();
      }
    }
  }

  public void addchangebuffer(short[] rawdata, int readsize){
    mchangebuffers.add(new changebuffer(rawdata, readsize));
  }

  private class changebuffer{
    private short[] rawdata;
    private int readsize;
    public changebuffer(short[] rawdata, int readsize){
      this.rawdata = rawdata.clone();
      this.readsize = readsize;
    }
    public short[] getdata(){
      return rawdata;
    }
    public int getreadsize(){
      return readsize;
    }
  }
}

simplelame 主要的逻辑是通过jni调用lame库

public class simplelame {

  public native static void close();

  public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);

  public native static int flush(byte[] mp3buf);

  public native static void init(int insamplerate, int outchannel, int outsamplerate, int outbitrate, int quality);

  public static void init(int insamplerate, int outchannel, int outsamplerate, int outbitrate) {
    init(insamplerate, outchannel, outsamplerate, outbitrate, 7);
  }
}

#include <cwchar>
#include "simplelame.h"
#include "lamemp3/lame.h"

static lame_global_flags *glf = null;

void java_com_clam314_lame_simplelame_close(jnienv *env, jclass type){
  lame_close(glf);
  glf = null;
}

jint java_com_clam314_lame_simplelame_encode(jnienv *env, jclass type, jshortarray buffer_l_,
                    jshortarray buffer_r_, jint samples, jbytearray mp3buf_) {
  jshort *buffer_l = env->getshortarrayelements(buffer_l_, null);
  jshort *buffer_r = env->getshortarrayelements(buffer_r_, null);
  jbyte *mp3buf = env->getbytearrayelements(mp3buf_, null);

  const jsize mp3buf_size = env->getarraylength(mp3buf_);

  int result =lame_encode_buffer(glf, buffer_l, buffer_r, samples, (u_char*)mp3buf, mp3buf_size);

  env->releaseshortarrayelements(buffer_l_, buffer_l, 0);
  env->releaseshortarrayelements(buffer_r_, buffer_r, 0);
  env->releasebytearrayelements(mp3buf_, mp3buf, 0);

  return result;
}

jint java_com_clam314_lame_simplelame_flush(jnienv *env, jclass type, jbytearray mp3buf_) {
  jbyte *mp3buf = env->getbytearrayelements(mp3buf_, null);

  const jsize mp3buf_size = env->getarraylength(mp3buf_);

  int result = lame_encode_flush(glf, (u_char*)mp3buf, mp3buf_size);

  env->releasebytearrayelements(mp3buf_, mp3buf, 0);

  return result;
}

void java_com_clam314_lame_simplelame_init__iiiii(jnienv *env, jclass type, jint insamplerate, jint outchannel,
                       jint outsamplerate, jint outbitrate, jint quality) {
  if(glf != null){
    lame_close(glf);
    glf = null;
  }
  glf = lame_init();
  lame_set_in_samplerate(glf, insamplerate);
  lame_set_num_channels(glf, outchannel);
  lame_set_out_samplerate(glf, outsamplerate);
  lame_set_brate(glf, outbitrate);
  lame_set_quality(glf, quality);
  lame_init_params(glf);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。