美文网首页
android 视频 mediacodec 播放视频文件

android 视频 mediacodec 播放视频文件

作者: XX杰 | 来源:发表于2018-11-21 15:50 被阅读0次

    本博客仅限播放视频文件,没有快进快退,暂停等功能

    1、开启线程,视频通道和音频通道要分开,
    2、本code视频和音频没有做到同步
    3、int outputBufferIndex = mediaCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_USEC); 开始绘制,
    4、注解,主要看视频处理通道

    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.media.AudioFormat;
    import android.media.AudioManager;
    import android.media.AudioTrack;
    import android.media.MediaCodec;
    import android.media.MediaCodecInfo;
    import android.media.MediaExtractor;
    import android.media.MediaFormat;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.Surface;
    
    import com.yang.testapp.interfaces.MediaCallback;
    
    import java.io.IOException;
    import java.nio.ByteBuffer;
    
    public class MediaCodecUtils {
    
        private static final String TAG = "MediaCodecUtils";
        final int TIMEOUT_USEC = 10000;   // 10 毫秒
        private boolean isPlaying = false;
        private Surface surface;
    
        private VideoThread videoThread;
        private AudioThread audioThread;
        private String mediaPath;
        private MediaCallback callback;
    
        // 处理视频通道
        private class VideoThread extends Thread {
    
            private boolean isVideoOver = false;
            int frameIndex = 0;
    
            @Override
            public void run() {
                try {
                    MediaExtractor videoExtractor = new MediaExtractor();
                    MediaCodec mediaCodec = null;
                    videoExtractor.setDataSource(mediaPath);
                    // 获得视频所在的 轨道
                    int trackIndex = getMediaTrackIndex(videoExtractor, "video/");
                    if (trackIndex >=0) {
                        MediaFormat format = videoExtractor.getTrackFormat(trackIndex);
                        // 指定解码后的帧格式
                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatRGBFlexible);
    
                        String mimeType = format.getString(MediaFormat.KEY_MIME);
                        int width = format.getInteger(MediaFormat.KEY_WIDTH);
                        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
                        // duration 是微秒 1 毫秒 = 1000微秒
                        long duration = format.getLong(MediaFormat.KEY_DURATION);
                        if (callback != null) {
                            callback.getMediaBaseMsg(width, height, duration);
                        }
    
                        // 切换到视频信道
                        videoExtractor.selectTrack(trackIndex);
                        // 创将解码视频的MediaCodec,解码器
                        mediaCodec = MediaCodec.createDecoderByType(mimeType);
                        // 配置绑定 surface
                        mediaCodec.configure(format, surface, null, 0);
                    }
    
                    if (mediaCodec == null) {
                        Log.v(TAG, "MediaCodec null");
                        return;
                    }
                    mediaCodec.start();
    
                    // 开始循环,一直到视频资源结束
                    MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
                    ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();  // 用来存放媒体文件的数据
                    ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();  // 解码后的数据
                    long startMs = System.currentTimeMillis();  // 开始的时间
                    // 当前Thread 没有被中断
                    while (!Thread.interrupted()) {
                        if (!isPlaying) {
                            continue;
                        }
    
                        if (!isVideoOver) {
                            // 视频没有结束  提取一个单位的视频资源放到 解码器(mediaCodec) 缓冲区中
                            isVideoOver = putBufferToMediaCodec(videoExtractor, mediaCodec, inputBuffers);
                        }
    
                        // 返回一个被成功解码的buffer的 index 或者是一个信息  同时更新 videoBufferInfo 的数据
                        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_USEC);
                        switch (outputBufferIndex) {
                            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                                Log.v(TAG, "format changed");
                                break;
                            case MediaCodec.INFO_TRY_AGAIN_LATER:
                                Log.v(TAG, "超时");
                                break;
                            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                                //outputBuffers = videoCodec.getOutputBuffers();
                                Log.v(TAG, "output buffers changed");
                                break;
                            default:
                                //直接渲染到Surface时使用不到outputBuffer
    //                            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                //延时操作
                                //如果缓冲区里的可展示时间>当前视频播放的总时间,就休眠一下 展示当前的帧,
                                sleepRender(videoBufferInfo, startMs);
    
                                //渲染为true就会渲染到surface   configure() 设置的surface
                                mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
                                frameIndex ++;
                                Log.v(TAG, "frameIndex   " + frameIndex);
    
                                break;
                        }
    
                        if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            Log.v(TAG, "buffer stream end");
                            break;
                        }
                    }
    
                    mediaCodec.stop();
                    mediaCodec.release();
                    videoExtractor.release();
    
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private static boolean isSemiPlanarYUV(int colorFormat) {
            switch (colorFormat) {
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                    return false;
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                    return true;
                default:
                    throw new RuntimeException("unknown format " + colorFormat);
            }
        }
    
        // 处理音频通道
        private class AudioThread extends Thread {
    
            private int audioInputBufferSize;
    
            private AudioTrack audioTrack;
    
            @Override
            public void run() {
                MediaExtractor audioExtractor = new MediaExtractor();
                MediaCodec audioCodec = null;
                try {
                    audioExtractor.setDataSource(mediaPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
                    MediaFormat mediaFormat = audioExtractor.getTrackFormat(i);
                    String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
                    if (mime.startsWith("audio/")) {
                        audioExtractor.selectTrack(i);
                        int audioChannels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                        int audioSampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                        int minBufferSize = AudioTrack.getMinBufferSize(audioSampleRate,
                                (audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO),
                                AudioFormat.ENCODING_PCM_16BIT);
                        int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                        audioInputBufferSize = minBufferSize > 0 ? minBufferSize * 4 : maxInputSize;
                        int frameSizeInBytes = audioChannels * 2;
                        audioInputBufferSize = (audioInputBufferSize / frameSizeInBytes) * frameSizeInBytes;
                        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                                audioSampleRate,
                                (audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO),
                                AudioFormat.ENCODING_PCM_16BIT,
                                audioInputBufferSize,
                                AudioTrack.MODE_STREAM);
                        audioTrack.play();
                        Log.v(TAG, "audio play");
                        //
                        try {
                            audioCodec = MediaCodec.createDecoderByType(mime);
                            audioCodec.configure(mediaFormat, null, null, 0);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        break;
                    }
                }
                if (audioCodec == null) {
                    Log.v(TAG, "audio decoder null");
                    return;
                }
                audioCodec.start();
                //
                final ByteBuffer[] buffers = audioCodec.getOutputBuffers();
                int sz = buffers[0].capacity();
                if (sz <= 0)
                    sz = audioInputBufferSize;
                byte[] mAudioOutTempBuf = new byte[sz];
    
                MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
                ByteBuffer[] inputBuffers = audioCodec.getInputBuffers();
                ByteBuffer[] outputBuffers = audioCodec.getOutputBuffers();
                boolean isAudioEOS = false;
                long startMs = System.currentTimeMillis();
    
                while (!Thread.interrupted()) {
                    if (!isPlaying) {
                        continue;
                    }
                    if (!isAudioEOS) {
                        isAudioEOS = putBufferToMediaCodec(audioExtractor, audioCodec, inputBuffers);
                    }
                    //
                    int outputBufferIndex = audioCodec.dequeueOutputBuffer(audioBufferInfo, TIMEOUT_USEC);
                    switch (outputBufferIndex) {
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                            Log.v(TAG, "format changed");
                            break;
                        case MediaCodec.INFO_TRY_AGAIN_LATER:
                            Log.v(TAG, "超时");
                            break;
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            outputBuffers = audioCodec.getOutputBuffers();
                            Log.v(TAG, "output buffers changed");
                            break;
                        default:
                            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                            //延时操作
                            //如果缓冲区里的可展示时间>当前视频播放的进度,就休眠一下
                            sleepRender(audioBufferInfo, startMs);
                            if (audioBufferInfo.size > 0) {
                                if (mAudioOutTempBuf.length < audioBufferInfo.size) {
                                    mAudioOutTempBuf = new byte[audioBufferInfo.size];
                                }
                                outputBuffer.position(0);
                                outputBuffer.get(mAudioOutTempBuf, 0, audioBufferInfo.size);
                                outputBuffer.clear();
                                if (audioTrack != null)
                                    audioTrack.write(mAudioOutTempBuf, 0, audioBufferInfo.size);
                            }
                            //
                            audioCodec.releaseOutputBuffer(outputBufferIndex, false);
                            break;
                    }
    
                    if ((audioBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        Log.v(TAG, "buffer stream end");
                        break;
                    }
                }//end while
                audioCodec.stop();
                audioCodec.release();
                audioExtractor.release();
                audioTrack.stop();
                audioTrack.release();
            }
        }
    
        public void  prepareMediaFile(String path, Surface layout, MediaCallback callback) {
            this.mediaPath = path;
            this.callback = callback;
            this.surface = layout;
        }
    
        public void playMediaFile() {
            isPlaying = true;
            if (videoThread == null) {
                videoThread = new VideoThread();
                videoThread.start();
            }
            if (audioThread == null) {
                audioThread = new AudioThread();
    //            audioThread.start();
            }
        }
    
        public void stopMediaFile() {
            isPlaying = false;
        }
    
        //获取指定类型媒体文件所在轨道
        private int getMediaTrackIndex(MediaExtractor videoExtractor, String MEDIA_TYPE) {
            int trackIndex = -1;
            // 获得轨道数量
            int trackNum = videoExtractor.getTrackCount();
            for (int i = 0; i < trackNum; i++) {
                MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);
                String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith(MEDIA_TYPE)) {
                    trackIndex = i;
                    break;
                }
            }
            return trackIndex;
        }
    
    
        /**
         * 将缓冲区传递至解码器
         * 如果到了文件末尾,返回true;否则返回false
         */
        private boolean putBufferToMediaCodec(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] inputBuffers) {
            boolean isMediaEOS = false;
            // 解码器  要填充有效数据的输入缓冲区的索引 —————— 此id的缓冲区可以被使用
            int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                // MediaExtractor读取媒体文件的数据,存储到缓冲区中。并返回大小。结束为-1
                int sampleSize = extractor.readSampleData(inputBuffer, 0);
                if (sampleSize < 0) {
                    decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    isMediaEOS = true;
                    Log.v(TAG, "media eos");
                } else {
                    // 在输入缓冲区添加数据之后,把它告诉 MediaCodec (解码)
                    decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                    // MediaExtractor 准备下一个 单位的数据
                    boolean ad = extractor.advance();
                    if (!ad) {
                        isMediaEOS = false;
                    }
                }
            } else {
                // 缓冲区不可用
            }
            return isMediaEOS;
        }
    
        private void sleepRender(MediaCodec.BufferInfo audioBufferInfo, long startMs) {
            // 这里的时间是 毫秒  presentationTimeUs 的时间是累加的 以微秒进行一帧一帧的累加
            // audioBufferInfo 是改变的
            while (audioBufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                try {
                    // 10 毫秒
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:android 视频 mediacodec 播放视频文件

          本文链接:https://www.haomeiwen.com/subject/uqscqqtx.html