这里有一个音视频同步的系列博客,写得很好,贴出来以备后用:
https://blog.csdn.net/nonmarking/article/details/78747369
下面是我自己实现的一个小demo:
首先是MyMediaPlayer这个类,主要用于编解码器的初始化和提供对外层的调用:
package com.com.leilu.mediacodec;
import android.media.MediaCodec;
import android.view.Surface;
import java.io.IOException;
/**
* Created by ll on 2018/5/17.
*/
public class MyMediaPlayer implements IPlayer {
private MyMediaExtractor mVideoExtractor;
private MyMediaExtractor mAudioExtractor;
private MediaCodec mVideoMediaCodec;
private MediaCodec mAudiMediaCodec;
private OnPlayingStateListener mOnPlayingStateListener;
private Surface mSurface;
private DecodeThread mVideoDecodeThread;
private DecodeThread mAudioDecodeThread;
public MyMediaPlayer(String path) {
mVideoExtractor = new MyMediaExtractor(path, false);
mAudioExtractor = new MyMediaExtractor(path, true);
}
@Override
public void setSurface(Surface surface) {
mSurface = surface;
}
@Override
public void init() {
try {
mVideoExtractor.init();
initVideoMediaCodec();
mAudioExtractor.init();
initAudioMediaCodec();
} catch (IOException e) {
e.printStackTrace();
}
}
private void initAudioMediaCodec() throws IOException {
mAudiMediaCodec = MediaCodec.createDecoderByType(mAudioExtractor.getMimeType());
mAudiMediaCodec.configure(mAudioExtractor.getMediaFormat(), null, null, 0);
mAudiMediaCodec.start();
}
private void initVideoMediaCodec() throws IOException {
mVideoMediaCodec = MediaCodec.createDecoderByType(mVideoExtractor.getMimeType());
mVideoMediaCodec.configure(mVideoExtractor.getMediaFormat(), mSurface, null, 0);
mVideoMediaCodec.start();
}
@Override
public void resume() {
}
@Override
public void start() {
mVideoDecodeThread = new DecodeThread(mVideoMediaCodec, mVideoExtractor, false);
mVideoDecodeThread.start();
mAudioDecodeThread = new DecodeThread(mAudiMediaCodec, mAudioExtractor, true);
mAudioDecodeThread.start();
}
@Override
public void stop() {
mVideoDecodeThread.stopDecode();
mAudioDecodeThread.stopDecode();
}
@Override
public void pause() {
}
@Override
public void setOnPlayingStateListener(OnPlayingStateListener listener) {
mOnPlayingStateListener = listener;
}
}
MyMediaExtractor类:对MediaExtractor进行了一层包装,主要是进行初始化操作,获取到视频文件对
应的流轨道信息和MediaFormat等数据
package com.com.leilu.mediacodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import java.io.IOException;
/**
* 对MediaExtractor进行了一层包装,主要是进行初始化操作,获取到视频文件对应的流轨道信息和MediaFormat等数据
* Created by ll on 2018/5/17.
*/
public class MyMediaExtractor {
private MediaExtractor mMediaExtractor;
private String mPath;
private boolean mIsAudioExtractor = true;
private MediaFormat mMediaFormat;
private int mTrackIndex = -1;
private String mMimeType;
public MyMediaExtractor(String path, boolean isAudioExtractor) {
mPath = path;
mIsAudioExtractor = isAudioExtractor;
}
public void init() throws IOException {
mMediaExtractor = new MediaExtractor();
mMediaExtractor.setDataSource(mPath);
int trackCount = mMediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(i);
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mIsAudioExtractor && mimeType.startsWith("audio/")) {
mMediaExtractor.selectTrack(i);
mTrackIndex = i;
mMimeType = mimeType;
mMediaFormat = mediaFormat;
} else if (!mIsAudioExtractor && mimeType.startsWith("video/")) {
mMediaExtractor.selectTrack(i);
mTrackIndex = i;
mMimeType = mimeType;
mMediaFormat = mediaFormat;
}
}
if (mTrackIndex == -1) {
throw new RuntimeException("Have no audio or video track!");
}
}
public String getMimeType() {
return mMimeType;
}
public MediaFormat getMediaFormat() {
return mMediaFormat;
}
public MediaExtractor getMediaExtracor() {
return mMediaExtractor;
}
public int getTrackIndex() {
return mTrackIndex;
}
}
DecodeThread类:解码线程,在里面做界面和播放或者渲染操作
package com.com.leilu.mediacodec;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.os.SystemClock;
import java.nio.ByteBuffer;
/**
* 解码线程,在里面做界面和播放或者渲染操作
* Created by ll on 2018/5/17.
*/
public class DecodeThread extends Thread {
private MediaCodec mMediaCodec;
private MyMediaExtractor mMediaExtractor;
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private boolean mIsRunning;
private boolean mIsAudio = true;// 默认是音频的界面线程
private MyAudioTrack mMyAudioTrack;// 音频播放工具
public DecodeThread(MediaCodec mediaCodec, MyMediaExtractor mediaExtractor, boolean isAudio) {
mMediaCodec = mediaCodec;
mMediaExtractor = mediaExtractor;
mIsAudio = isAudio;
mInputBuffers = mMediaCodec.getInputBuffers();
mOutputBuffers = mMediaCodec.getOutputBuffers();
if (isAudio) {// 如果是音频,则初始化AudioTrack
mMyAudioTrack = new MyAudioTrack(mediaExtractor.getMediaFormat());
}
}
@Override
public void run() {
mIsRunning = true;
MediaExtractor mediaExtractor = mMediaExtractor.getMediaExtracor();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 拿到开始解码的时间
long startTime = System.currentTimeMillis();
while (mIsRunning) {
try {
// 将解码数据填到inputBuffer中
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex < 0) {
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, mediaExtractor.getSampleTime(), MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
ByteBuffer byteBuffer = mInputBuffers[inputBufferIndex];
byteBuffer.clear();
int sampleSize = mediaExtractor.readSampleData(byteBuffer, 0);
if (sampleSize <= 0) {
break;
}
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0);
mediaExtractor.advance();
}
// 将解码数据进行选择或者播放
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
mOutputBuffers = mMediaCodec.getOutputBuffers();
} else if (outputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
// 这里拿到pts和开始解码到当前时间的时间差,如果时间差大于0,则睡眠对应的时间差
long deltaTime = bufferInfo.presentationTimeUs / 1000 - (System.currentTimeMillis() - startTime);
if (deltaTime > 0) {
SystemClock.sleep(deltaTime);
}
ByteBuffer outputBuffer = mOutputBuffers[outputBufferIndex];
if (mIsAudio) {// 如果是音频则播放音频
mMyAudioTrack.writeData(outputBuffer, bufferInfo.size);
}
mMediaCodec.releaseOutputBuffer(outputBufferIndex, !mIsAudio);
}
} catch (IllegalStateException e) {
break;
}
}
mIsRunning = false;
mediaExtractor.release();
mMediaCodec.stop();
mMediaCodec.release();
}
public void stopDecode() {
mIsRunning = false;
}
}
使用:
String path = "/sdcard/SNH48.mp4";
MyMediaPlayer myMediaPlayer = new MyMediaPlayer(path);
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
myMediaPlayer.setSurface(holder.getSurface());
myMediaPlayer.init();
myMediaPlayer.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
这只是一个学习MediaCodec解码的简单例子,作为笔记使用,目前并没有实现音频同步播放,
音频和视频都自己开一个线程不断的进行播放,根据pts和当前的时间进行比较,如果有时间
差则睡眠相应的毫秒数,肯定会随着不同手机硬件的不同音频同步有一定的偏差,并且通过休眠的
方式也非常的不妥,这严重依赖了cpu的时间片的分配,势必导致音频的不同步现象,但是播放
几分钟的视频应该不会出现太大的误差,这个程序就当着一个娱乐就好,有时间再继续研究音视频
同步相关知识