美文网首页
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 播放视频文件

    本博客仅限播放视频文件,没有快进快退,暂停等功能 1、开启线程,视频通道和音频通道要分开,2、本code视频和音频...

  • Android高级进阶汇总

    android MediaCodec解析 Android音视频播放器框架 Android 项目热门技术 Andro...

  • FFmpeg4Android:视频文件解码

    4 FFmpeg4Android:视频解码 4.1 视频解码流程 a) 视频播放流程视频播放器播放视频文件,需要经...

  • Android 播放视频文件

    在 Android 6.0 或者之后的版本中要主动请求用户授权,具体方法,可以参考我之前写的这篇文章《Androi...

  • 几种播放视频文件的方式

    几种播放视频文件的方式(一) —— 总结播放视频的几种方式(一)几种播放视频文件的方式(二) —— 基于Media...

  • FFmpeg4Android:视频播放

    5 FFmpeg4Android:视频播放 视频文件的播放过程,就是将视频中的压缩数据解码成一帧帧的RGB数据,绘...

  • Android 音视频学习

    概述音频的采集与播放视频的采集与渲染MediaCodec 硬编解码MediaCodec 硬编 aac 参考 音视频...

  • 音视频学习(一)-- 基础知识准备

    本章知识点一览: 视频播放原理视频文件封装格式音视频编码方式简介 一、视频播放器原理: 我们播放的视频文件一般都是...

  • MediaCodec、OpenGL、OpenSL/AudioTr

    概述 功能很简单,大致流程为: MediaCodec 解码视频文件得到 YUV、PCM 数据 OpenGL 将 Y...

  • Android VideoView简单播放视频

    给Android VideoView一个文件目录,就可以直接播放智能设备中的视频文件,现在以播放事先用手机拍好并重...

网友评论

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

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