MediaCodec異步處理,之前那方法是同步處理,之前文章我說錯了,寫入影音文件應該說編碼器才對,處理讀取影音文件才是解碼器,編碼解碼原理都一樣,
試著將錄取手機螢幕影像,之前程式改成MediaCodec異步處理,異步處理比同步處理更為直覺,寫法比較清楚,比較符合物件導向,我比較喜歡這樣的寫法.
說一下MediaCodec異步處理寫法原理,我喜歡用extends然後覆寫其中方法,總覺得這樣寫法才優雅,我們首先extends MediaCodec.Callback,然後覆寫其中4個方法Method(如下面),在啟動MediaCodec前MediaCodec.setCallback(this)設定回傳Class為this,MediaCodec.start()開始後,這4個覆寫方法,就會開始時時監聽MediaCodec狀態,
1. onInputBufferAvailable監聽輸入端buffer可獲取,
2. onOutputBufferAvailable監聽輸出端buffer可獲取,
3. onOutputFormatChanged監聽格式發生變化,
4. onError監聽發生錯誤,
當然在這之前要先格式化MediaCodec,準備好代為處理輸入端的VirtualDisplay,方法中codec參數和index參數,info參數,就是監聽MediaCodec後的數據值,用這些參數來處理我們想要的資料,codec參數指該MediaCodec,index參數指MediaCodec索引值,info參數指MediaCodec buffer資訊,參數format指MediaCodec 格式發生變化format(onOutputFormatChange)
譬如codec.getOutputBuffer(index)就能取得輸出buffer,info.size()取得buffer size值
//1 監聽輸入端buffer可獲取
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
//codec索引值index,處理codec
//這裡沒用到,Virtualdisplay已取代輸入端
}
//2 監聽輸出端buffer可獲取
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
Log.d("TAG","Output buffer begin.");
ByteBuffer encodeData = codec.getOutputBuffer(index);
........................................................
codec.releaseOutputBuffer(index, false);//觸理完一回,一定要釋放輸出buffer
}
//3 監聽codec發生錯誤重設codec
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.d("TAG","Error:"+e);
codec.reset();
}
//4 監聽格式發生變化重設格式,啟動合成器MediaMuxer
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d("TAG","output format already changed!");
//vedio resetOutputFormat
//MediaFormat newFormat = codec.getOutputFormat();
//MediaMuxer addtrack
mVideoTrackIndex = mMediaMuxer.addTrack(format);
mMediaMuxer.start();
}
關鍵碼,部份拿掉,這樣流程較清楚
public class surface_mediacodec_advance extends MediaCodec.Callback{
//private Boolean MUXER_START = false;
private MediaProjection mp;
private AtomicBoolean mQuit = new AtomicBoolean(false);
private int mscreendi;
private VirtualDisplay vp;
private String save_path = "/sdcard/capture.mp4";
private check_file_save_finish mcheck_file_save_finish;
private long lastpresentationTimeUs;
private int VIDEO_KEY_BIT_RATE = 4000000;//2000000 4000000
private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
private static final int FRAME_RATE = 30; // 30 fps
private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
private static final int TIMEOUT_US = 10000;
private int CH_TYPE;
//使用者已許可MediaProjection mp
public surface_mediacodec_advance(int windowWidth, int windowHeight, int sdi, MediaProjection m, int audio_sample_rate, int ch) {
CH_TYPE=ch;
//初始化MediaCodec
mwindowWidth = windowWidth;
mwindowHeight = windowHeight;
mp = m;
mscreendi = sdi;
try {
mMediaMuxer= new MediaMuxer(save_path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
throw new RuntimeException(e);
}
Log.d("TAG"," CH TYPE :"+CH_TYPE);
}
//開始錄製
public final void START_RECORDER() {
preparerecorder_other();//設置MediaCodec video格式
//preapareaudioencoder();//設置MediaCodec audio
vp = mp.createVirtualDisplay("record_screen",
mwindowWidth, mwindowHeight, mscreendi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mSurface, null, null);
}
//停止錄製
public final void STOP_RECORDER() {
release();//停止要release
}
//設置mMediaCodec格式
private void preparerecorder_other() {
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mwindowWidth, mwindowHeight);
//format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
//MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_KEY_BIT_RATE);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
Log.d("TAG", "created video format: " + format);
try {
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
} catch (IOException e) {
}
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mSurface = mMediaCodec.createInputSurface();
Log.d("TAG", "created input surface: " + mSurface);
//設定MediaCodec回傳Class
mMediaCodec.setCallback(this);
mMediaCodec.start();
}
//release Mediacodec這順序要對否則格式會錯
private void release() {
mcheck_file_save_finish=new check_file_save_finish(save_path);
while (!mcheck_file_save_finish.check_file_save_finish()){
Log.d("TAG_NO","NO FINISH");
}
if (vp != null) {
vp.release();
}
if (mMediaMuxer != null) {
mMediaMuxer.stop();
mMediaMuxer.release();
mMediaMuxer = null;
}
//
if (mMediaCodec != null) {
mMediaCodec.stop();
mMediaCodec.release();
mMediaCodec= null;
}
//監聽輸入buffer可獲取
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
//這裡沒用到,Virtualdisplay已取代輸入端
}
//監聽輸出buffer可獲取
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
Log.d("TAG","Output buffer begin.");
ByteBuffer encodeData = codec.getOutputBuffer(index);
if ((info.flags & codec.BUFFER_FLAG_CODEC_CONFIG) != 0) {//編碼數據,非媒體數據,忽略它
info.size = 0;
}
if (info.size == 0) {//編碼數據,非媒體數據,忽略它
encodeData = null;
Log.d("TAG", "Ignore Data");
} else { //非媒體數據,產生提示
Log.d("TAG", "Get Info: size="
+ info.size + " Video presentationTimeUs="
+ info.presentationTimeUs + " offset="
+ info.offset);
}
if (encodeData != null) { //媒體數據寫入
encodeData.position(info.offset);
encodeData.limit(info.offset + info.size);
mMediaMuxer.writeSampleData(mVideoTrackIndex, encodeData, info);
Log.d("TAG", "Send Info: size=" + info.size);
}
codec.releaseOutputBuffer(index, false);//釋放輸出buffer
}
//codec發生錯誤重設codec
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.d("TAG","Error:"+e);
codec.reset();
}
//格式發生變化重設格式
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d("TAG","output format already changed!");
//vedio resetOutputFormat
//MediaFormat newFormat = codec.getOutputFormat();
//MediaMuxer addtrack
mVideoTrackIndex = mMediaMuxer.addTrack(format);
mMediaMuxer.start();
}
}
據上面錄製手機改影像編碼器為異步處理後,接著又完成了MediaCodec異步處理音頻編碼,音頻編碼異步流程,同影像編碼只是多處理輸入端
監聽輸入onInputBufferAvailable中去讀取MIC麥克風buffer資料,getInputBuffer(index)取得MediaCodec輸入buffer,將資料填入這buffer,然後queueInputBuffer(),此時onOutputBufferAvailable監聽輸出buffer可獲取,透過界面送到影像編碼寫入合成器,然後釋放此buffer,然後監聽輸入onInputBufferAvailable變又可獲取,重複上面,又去讀取MIC麥克風buffer資料.................
音頻編碼器,設計一個界面capture_audio_outpu_buffer,含實作2個方法
//捕捉輸出buffer資料,以便寫入合成器
void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info);
//audio MediaCodec初始化MediaFormat,以便產生AudioTrackIndex,使能夠啟動合成
void on_capture_mediaformat(MediaFormat mMediaFormat);
影像編碼器,實作上面界面的2個方法,影像編碼器在監聽onOutputFormatChanged中實例化音頻編碼器,set_capture_audio_outpu_buffer_listener監聽this,start()後on_capture_mediaformat會產生AudioTrackIndex,on_capture_audio_outpu_buffer開始監聽音頻編碼器的輸出資料,寫入合成器,並啟動合成器
後記:遇到有些手機AudioRecoderformat 設置立體聲,聲道數不同錄製完,回放影片聲音會變快,看一下程式就知道,是因為Audio取樣時,buffer length比getInputBuffer(index)的剩餘長度,還長,沒完全填入,只要分次全部填入就解掉了
影像編碼
public class surface_mediacodec_advance extends MediaCodec.Callback implements audio_encoder_advance.capture_audio_outpu_buffer{
.........................................
///////////////////實作 audio_encoder_advance.capture_audio_outpu_buffer Method///////////////////
//監聽audio_encoder_advance MediaFormat初始化一次
@Override
public void on_capture_mediaformat(MediaFormat mMediaFormat){
//產生AudioTrackIndex
mAudioTrackIndex = mMediaMuxer.addTrack(mMediaFormat);
}
//時時監聽audio_encoder_advance outpu buffer
@Override
public void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info){
//audio_encoder_advance輸出,寫入合成器
mMediaMuxer.writeSampleData(mAudioTrackIndex, encodedAudioData, info);
}
........................................................................................
//格式發生變化實例化音頻編碼器,set_capture_audio_outpu_buffer_listener,start()後會產生AudioTrackIndex,
//開始監聽audio_encoder_advance outpu buffer寫入合成器
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d("TAG_C","output format already changed!");
//vedio resetOutputFormat
//MediaFormat newFormat = codec.getOutputFormat();
//MediaMuxer addtrack
mVideoTrackIndex = mMediaMuxer.addTrack(format);
//這要通知其他mediacodec reset
if(maudio_encoder_advance==null){
Log.d("TAG"," SAMPLE RATE AUDO :"+maudio_sample_rate);
//實例化audio_encoder_advance
maudio_encoder_advance=new audio_encoder_advance(maudio_sample_rate,CH_TYPE);
//設定傾聽事件
maudio_encoder_advance.set_capture_audio_outpu_buffer_listener(this);
}
//audio_encoder_advance開始編碼
maudio_encoder_advance.start();
//若存在,resetOutputFormat,要在這reset其他format
//if(maudio_encoder_advance!=null){
// maudio_encoder_advance.resetOutputFormat();
//}
//啟動合成器
mMediaMuxer.start();
}
...............................................
}
音頻編碼
audio_encoder_advance class
public class audio_encoder_advance extends MediaCodec.Callback{
public void start(){
preapareaudioencoder
}
private void preapareaudioencoder() {
//設置audio MediaCodec格式
.............................
if(mlistener!=null) {
//設新newAudioFormat給監聽者,以便產生AudioTrackIndex
mlistener.on_capture_mediaformat(newAudioFormat);
}
//設回call
mAudioEncoder.setCallback(this);
mAudioEncoder.start();
}
....................................................................................
//時時監聽輸出buffer可獲取
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
...............................................................................
if(mlistener!=null) {
//設輸出資料給監聽者
mlistener.on_capture_audio_outpu_buffer(codec, encodedAudioData, info);
}
}
/////////////////////////////////界面////////////////////////////////////////////////////////////////
//interface變數
private capture_audio_outpu_buffer mlistener=null;
//設定listener
public void set_capture_audio_outpu_buffer_listener(capture_audio_outpu_buffer ca){
mlistener=ca;
}
//設計界面
public interface capture_audio_outpu_buffer{
//捕捉輸出buffer資料
void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info);
//audio MediaCodec初始化MediaFormat
void on_capture_mediaformat(MediaFormat mMediaFormat);
}
}
沒有留言:
張貼留言