EasyPlayer實現播放時同步錄像的功能

在之前一片博客《 EasyPusher實現安卓Android手機直播推送同步錄像功能》(http://blog.csdn.net/jyt0551/article/details/58714595)中,我寫到了EasyPusher推送的同時進行本地存儲的功能,我們今天來介紹下EasyPlayer保存本地錄像的功能。EasyPlayer同樣是運用MediaMuxer進行錄像的,與EasyPusher不同的是,Player要保存的是遠端的音視頻碼流。目前Player支持對H264格式的視頻和AAC格式的音頻進行存儲。

這裏寫圖片描述

在前一篇博客 ,音視頻碼流的metadata,即MediaFormat,是從MediaCodec取出來的。也就是說硬編碼庫提供了獲取音視頻的metadata的接口。但是很可惜我們在播放端並沒有這樣方便的藉口可以調用。那MediaFormat對象只能我們手動構建了。

MediaFormat這個類的實現非常簡單,它的內部以鍵值對的形式對音視頻的參數進行了封裝,並且向外提供了接口以供讀寫。因而我們可以創建一個MediaFormat對象,並使用特定的參數對其賦值即可。經作者研究發現,在錄像時,對於視頻流,需要的metadata如下表所示。

數據 說明
KEY_MIME 視頻的MIME,比如video/avc
width 寬度
height 高度
csd-0 SPS
csd-1 PPS

對於音頻,需要如下信息:

數據 說明
KEY_MIME 音頻的MIME,比如audio/mp4a-latm
KEY_CHANNEL_COUNT 通道數
KEY_SAMPLE_RATE 採樣率
csd-0 一些更多的細節信息,比如profile、sample的索引等。參考exoplayer裏的音頻數據的處理
csd-1 這個。。更多的細節,就不太清楚了。作者也是參考了exoplayer裏面的處理

瞭解了這些基本信息後,接下來我們要做的就是從碼流中獲取到這些信息,並構建MediaFormat,用來添加Video或Audio Track.
下面是獲取到視頻相關信息後,添加一個VideoTrack的代碼。

// 添加Video Track
MediaFormat format = new MediaFormat();
format.setInteger(MediaFormat.KEY_WIDTH, mWidth);
format.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
mCSD0.clear();
format.setByteBuffer("csd-0", mCSD0);
mCSD1.clear();
format.setByteBuffer("csd-1", mCSD1);
format.setString(MediaFormat.KEY_MIME, "video/avc");


Log.i(TAG, String.format("addTrack video track :%s", format));
mMuxerVideoTrack = muxer.addTrack(format);

下面是添加AudioTrack的代碼。

int audioObjectType = 2;
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig(audioObjectType, sampleRateIndex, channelConfig);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
//                                format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioParams.second);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioParams.first);

List<byte[]> bytes = Collections.singletonList(audioSpecificConfig);
for (int j = 0; j < bytes.size(); j++) {
    format.setByteBuffer("csd-" + j, ByteBuffer.wrap(bytes.get(j)));
}

Log.i(TAG, String.format("addTrack audio track :%s", format));
mMuxerAudioTrack = muxer.addTrack(format);

至此,音視頻的通道都已經添加完成,接下來就是要寫數據了。代碼如下:

private synchronized void pumpSample(RTSPClient.FrameInfo frameInfo) {
      if (mObject == null) return;
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
          MediaMuxer muxer = (MediaMuxer) mObject;
          MediaCodec.BufferInfo bi = new MediaCodec.BufferInfo();
          bi.offset = frameInfo.offset;
          bi.size = frameInfo.length;
          ByteBuffer buffer = ByteBuffer.wrap(frameInfo.buffer, bi.offset, bi.size);
          bi.presentationTimeUs = frameInfo.stamp;
          try {
              if (frameInfo.audio) {
                  bi.offset += 7;
                  bi.size -= 7;
                  if (mMuxerAudioTrack > -1)
                      muxer.writeSampleData(mMuxerAudioTrack, buffer, bi);
              } else if (mMuxerVideoTrack > -1) {
                  if (frameInfo.type != 1) {
                      bi.flags = 0;
                  } else {
                      bi.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
                  }
                  muxer.writeSampleData(mMuxerVideoTrack, buffer, bi);
              }
          } catch (IllegalStateException ex) {
              ex.printStackTrace();
          } catch (IllegalArgumentException ex) {
              ex.printStackTrace();
          }
      }
  }

最後,在錄像完成後,關閉MediaMuxer,釋放相關資源。


    public synchronized void stopRecord() {
        mMuxerAudioTrack = mMuxerVideoTrack = -1;
        mRecordingPath = null;
        if (mObject == null) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            MediaMuxer muxer = (MediaMuxer) mObject;
            try {
                muxer.release();
            } catch (IllegalStateException ex) {
                ex.printStackTrace();
            } catch (IllegalArgumentException ex) {
                ex.printStackTrace();
            }
        }
        mObject = null;

        ResultReceiver rr = mRR;
        if (rr != null) {
            rr.send(RESULT_RECORD_END, null);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章