使用MediaCodec+AudioTrack进行简单的音视频播放

这里有一个音视频同步的系列博客,写得很好,贴出来以备后用:
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的时间片的分配,势必导致音频的不同步现象,但是播放
几分钟的视频应该不会出现太大的误差,这个程序就当着一个娱乐就好,有时间再继续研究音视频
同步相关知识

你可能感兴趣的:(音视频)