2014-04-04 79 views
3

我试图使用Android 4.3中提供的AudioRecord,MediaCodecMediaMuxer录制音频和视频。但是,有时音频编码器线程会停止并且不再编码。结果是,一个破损的mp4文件,因为复用器没有收到任何编码的音频帧。在我的三星Galaxy Note 3上它的工作效率是99%,但是在我的索尼Xperia Z1上,编码线程始终卡住。我真的不知道是什么原因,也许有人可以帮我优化我的代码:在Android上使用MediaMuxer录制音频和视频

AudioRecorder.java

package com.cmdd.horicam; 

import java.nio.ByteBuffer; 

import android.media.AudioFormat; 
import android.media.AudioRecord; 
import android.media.MediaCodec; 
import android.media.MediaCodecInfo; 
import android.media.MediaFormat; 
import android.media.MediaRecorder; 
import android.os.Looper; 
import android.util.Log; 

public class AudioRecorder implements Runnable { 
    public static final String TAG = "AudioRecorder"; 
    public static final boolean VERBOSE = false; 

    public MovieMuxerAudioHandler mAudioHandler; 

    // audio format settings 
    public static final String MIME_TYPE_AUDIO = "audio/mp4a-latm"; 
    public static final int SAMPLE_RATE = 44100; 
    public static final int CHANNEL_COUNT = 1; 
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; 
    public static final int BIT_RATE_AUDIO = 128000; 
    public static final int SAMPLES_PER_FRAME = 1024; // AAC 
    public static final int FRAMES_PER_BUFFER = 24; 
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; 
    public static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; 

    public static final int MSG_START_RECORDING = 0; 
    public static final int MSG_STOP_RECORDING = 1; 
    public static final int MSG_QUIT = 2; 


    private MediaCodec mAudioEncoder; 
    private int iBufferSize; 
    int iReadResult = 0; 
    private boolean bIsRecording = false; 

    private static final int TIMEOUT_USEC = 10000; 

    private MovieMuxer mMovieMuxer;  

    private MediaFormat mAudioFormat; 

    private volatile AudioRecorderHandler mHandler; 

    private Object mReadyFence = new Object();  // guards ready/running 
    private boolean mReady; 
    private boolean mRunning; 

    public AudioRecorder(MovieMuxer mMovieMuxer){ 
     this.mMovieMuxer = mMovieMuxer; 
    } 

    /** 
    * Recorder thread entry point. Establishes Looper/Handler and waits for messages. 
    * <p> 
    * @see java.lang.Thread#run() 
    */ 
    @Override 
    public void run() { 
     // Establish a Looper for this thread, and define a Handler for it. 
     Looper.prepare(); 
     synchronized (mReadyFence) { 
      mHandler = new AudioRecorderHandler(this); 
      mReady = true; 
      mReadyFence.notify(); 
     } 
     Looper.loop(); 

     if(VERBOSE)Log.d(TAG, "audio recorder exiting thread"); 
     synchronized (mReadyFence) { 
      mReady = mRunning = false; 
      mHandler = null; 
     } 
    } 

    public void prepareEncoder(){ 
     // prepare audio format 
     mAudioFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, CHANNEL_COUNT); 
     mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 
     mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); 
     mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE_AUDIO); 

     mAudioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO); 
     mAudioEncoder.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
     mAudioEncoder.start();  

     new Thread(new AudioEncoderTask(), "AudioEncoderTask").start(); 
    } 

    public void prepareRecorder() {  
     int iMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); 

     bIsRecording = false; 

     iBufferSize = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER; 

     // Ensure buffer is adequately sized for the AudioRecord 
     // object to initialize 
     if (iBufferSize < iMinBufferSize) 
      iBufferSize = ((iMinBufferSize/SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2; 

     AudioRecord mAudioRecorder; 
     mAudioRecorder = new AudioRecord(
       AUDIO_SOURCE, // source 
       SAMPLE_RATE, // sample rate, hz 
       CHANNEL_CONFIG, // channels 
       AUDIO_FORMAT, // audio format 
       iBufferSize); // buffer size (bytes) 

     mAudioRecorder.startRecording(); 

     new Thread(new AudioRecorderTask(mAudioRecorder), "AudioRecorderTask").start(); 
    } 

    /** 
    * Tells the audio recorder to start recording. (Call from non-encoder thread.) 
    * <p> 
    * Creates a new thread, which will create an encoder using the provided configuration. 
    * <p> 
    * Returns after the recorder thread has started and is ready to accept Messages. The 
    * encoder may not yet be fully configured. 
    */ 
    public void startRecording() { 
     if(VERBOSE)Log.d(TAG, "audio recorder: startRecording()"); 
     synchronized (mReadyFence) { 
      if (mRunning) { 
       Log.w(TAG, "audio recorder thread already running"); 
       return; 
      } 
      mRunning = true; 

      new Thread(this, "AudioRecorder").start(); 
      while (!mReady) { 
       try { 
        mReadyFence.wait(); 
       } catch (InterruptedException ie) { 
        // ignore 
       } 
      } 
     } 

     mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING)); 
    } 

    public void handleStartRecording(){ 
     if(VERBOSE)Log.d(TAG, "handleStartRecording"); 
     prepareEncoder(); 
     prepareRecorder(); 
     bIsRecording = true; 
    } 

    /** 
    * Tells the video recorder to stop recording. (Call from non-encoder thread.) 
    * <p> 
    * Returns immediately; the encoder/muxer may not yet be finished creating the movie. 
    * <p> 
    */ 
    public void stopRecording() { 
     if(mHandler != null){ 
      mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING)); 
      mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT)); 
     } 
    } 

    /** 
    * Handles a request to stop encoding. 
    */ 
    public void handleStopRecording() { 
     if(VERBOSE)Log.d(TAG, "handleStopRecording"); 
     bIsRecording = false; 
    } 

    public String getCurrentAudioFormat(){ 
     if(this.mAudioFormat == null) 
      return "null"; 
     else 
      return this.mAudioFormat.toString(); 
    } 

    private class AudioRecorderTask implements Runnable { 

     AudioRecord mAudioRecorder; 
     ByteBuffer[] inputBuffers; 
     ByteBuffer inputBuffer; 

     public AudioRecorderTask(AudioRecord recorder){ 
      this.mAudioRecorder = recorder; 
     } 

     @Override 
     public void run() { 
      if(VERBOSE)Log.i(TAG, "AudioRecorder started recording"); 
      long audioPresentationTimeNs; 

      byte[] mTempBuffer = new byte[SAMPLES_PER_FRAME]; 

      while (bIsRecording) { 
       audioPresentationTimeNs = System.nanoTime(); 

       iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME); 
       if(iReadResult == AudioRecord.ERROR_BAD_VALUE || iReadResult == AudioRecord.ERROR_INVALID_OPERATION) 
        Log.e(TAG, "audio buffer read error"); 

       // send current frame data to encoder 
       try { 
        if(inputBuffers == null) 
         inputBuffers = mAudioEncoder.getInputBuffers(); 

        int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1); 
        if (inputBufferIndex >= 0) { 
         inputBuffer = inputBuffers[inputBufferIndex]; 
         inputBuffer.clear(); 
         inputBuffer.put(mTempBuffer); 
         //recycleInputBuffer(mTempBuffer); 

         if(VERBOSE)Log.d(TAG, "sending frame to audio encoder"); 
         mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, mTempBuffer.length, audioPresentationTimeNs/1000, 0); 
        } 
       } catch (Throwable t) { 
        Log.e(TAG, "sendFrameToAudioEncoder exception"); 
        t.printStackTrace(); 
       } 
      } 

      // finished recording -> send it to the encoder 
      audioPresentationTimeNs = System.nanoTime(); 

      iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME); 
      if (iReadResult == AudioRecord.ERROR_BAD_VALUE 
        || iReadResult == AudioRecord.ERROR_INVALID_OPERATION) 
       Log.e(TAG, "audio buffer read error"); 

      // send current frame data to encoder 
      try { 
       int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1); 
       if (inputBufferIndex >= 0) { 
        inputBuffer = inputBuffers[inputBufferIndex]; 
        inputBuffer.clear(); 
        inputBuffer.put(mTempBuffer); 

        if(VERBOSE)Log.d(TAG, "sending EOS to audio encoder"); 
        mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, mTempBuffer.length, audioPresentationTimeNs/1000, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
       } 
      } catch (Throwable t) { 
       Log.e(TAG, "sendFrameToAudioEncoder exception"); 
       t.printStackTrace(); 
      } 


      //if (mAudioRecorder != null) { 
      // mAudioRecorder.release(); 
      // mAudioRecorder = null; 
      // if(VERBOSE)Log.i(TAG, "stopped"); 
      //}   
     }  
    } 

    private class AudioEncoderTask implements Runnable { 
     private boolean bAudioEncoderFinished; 
     private int iAudioTrackIndex; 
     private MediaCodec.BufferInfo mAudioBufferInfo; 

     @Override 
     public void run(){ 
      if(VERBOSE)Log.i(TAG, "AudioEncoder started encoding"); 
      bAudioEncoderFinished = false; 

      ByteBuffer[] encoderOutputBuffers = mAudioEncoder.getOutputBuffers(); 
      ByteBuffer encodedData; 

      mAudioBufferInfo = new MediaCodec.BufferInfo(); 

      while(!bAudioEncoderFinished){    
       int encoderStatus = mAudioEncoder.dequeueOutputBuffer(mAudioBufferInfo, TIMEOUT_USEC); 
       if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 
        // no output available yet 
        if (VERBOSE) Log.d(TAG + "_encoder", "no output available, spinning to await EOS"); 
       } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
        // not expected for an encoder 
        encoderOutputBuffers = mAudioEncoder.getOutputBuffers(); 
       } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
        MediaFormat newFormat = mAudioEncoder.getOutputFormat(); 
        if(VERBOSE)Log.d(TAG, "received output format: " + newFormat); 
        // should happen before receiving buffers, and should only happen once 
        iAudioTrackIndex = mMovieMuxer.addTrack(newFormat); 

       } else if (encoderStatus < 0) { 
        Log.w(TAG + "_encoder", "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); 
        // let's ignore it 
       } else { 
        if(mMovieMuxer.muxerStarted()){ 
         encodedData = encoderOutputBuffers[encoderStatus]; 
         if (encodedData == null) { 
          throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); 
         } 

         if ((mAudioBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 
          // The codec config data was pulled out and fed to the muxer when we got 
          // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. 
          if (VERBOSE) Log.d(TAG + "_encoder", "ignoring BUFFER_FLAG_CODEC_CONFIG"); 
          mAudioBufferInfo.size = 0; 
         } 

         if (mAudioBufferInfo.size != 0) { 

          // adjust the ByteBuffer values to match BufferInfo (not needed?) 
          encodedData.position(mAudioBufferInfo.offset); 
          encodedData.limit(mAudioBufferInfo.offset + mAudioBufferInfo.size); 

          mMovieMuxer.writeSampleData(iAudioTrackIndex, encodedData, mAudioBufferInfo); 

          if (VERBOSE) { 
           Log.d(TAG + "_encoder", "sent " + mAudioBufferInfo.size + " bytes (audio) to muxer, ts=" + mAudioBufferInfo.presentationTimeUs); 
          } 
         } 

         mAudioEncoder.releaseOutputBuffer(encoderStatus, false); 

         if ((mAudioBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
          // reached EOS 
          if(VERBOSE)Log.i(TAG + "_encoder", "audio encoder finished"); 
          bAudioEncoderFinished = true; 

          // tell the muxer that we are finished 
          mAudioHandler.onAudioEncodingFinished(); 
          break; 
         } 
        } 
       } 
      } 
     } 

    } 
} 

感谢您的帮助。

+0

可能相关:http://stackoverflow.com/questions/22604756/(我想知道如果“转换为立体声”的答案会帮助)。 – fadden

+0

试了一下..仍然没有工作 – chris6523

回答

1

当你请求从音频记录数据:

iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME); 

你可以得到几帧,然后PTS预测在mediacodec将基于帧的数量和压缩的帧持续时间产生适当的输出点。然后你可以在编码器出队输出缓冲后打印这些时间戳,看到实际值将为!0。但是,您将在输入处再次以0点的速度馈送编码器,并且它将重置内部预测。这一切都将导致非单调的生成代码,并且可能合成器已经为此抱怨,请检查adb日志。对我来说,这是发生了,我必须在喂入编码器之前设置采样时间。

mTempBuffer.setSampleTime(calc_pts_for_that_frame); 

至少你可以检查,如果这是你正面临着一个问题,如果是这样就容易解决了计算appropiate时间戳。

0

document表示在读入byte[]时,AudioFormat应为ENCODING_PCM_8BIT。如果要使用ENCODING_PCM_16BIT,请尝试使用reading into a ByteBuffer,但请记住在数据队列中将用户iReadResult作为size

//Creating ByteBuffer 
ByteBuffer mTempBuffer = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME); 

//In reading loop 
mTempBuffer.clear(); 
iReadResult = mAudioRecorder.read(mTempBuffer, SAMPLES_PER_FRAME); 

//Send to encoder 
mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, iReadResult, audioPresentationTimeNs/1000L, 0);