服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - 使用MediaCodec实现视频解码播放

使用MediaCodec实现视频解码播放

2024-03-14 15:16沐雨花飞蝶 Android

MediaCodec是Android平台上的一个多媒体编解码器,用于对音频和视频数据进行编解码。它可以实现高效的音视频编解码,并且可以与硬件加速器结合使用,提高编解码性能。MediaCodec可以用于录制和播放音视频,以及进行实时的音视频

MediaCodec是Android平台上的一个多媒体编解码器,用于对音频和视频数据进行编解码。它可以实现高效的音视频编解码,并且可以与硬件加速器结合使用,提高编解码性能。MediaCodec可以用于录制和播放音视频,以及进行实时的音视频通信等场景。

MediaCodec常用的方法:

  1. createDecoderByType(String mimeType):根据指定的MIME类型创建解码器。
  2. createEncoderByType(String mimeType):根据指定的MIME类型创建编码器。
  3. configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags):配置解码器或编码器的参数,包括媒体格式、渲染表面、加密等。
  4. start():启动解码器或编码器。
  5. flush():清空解码器或编码器的输入和输出缓冲区。
  6. release():释放解码器或编码器的资源。

MediaCodec解码过程:

  1. 创建MediaCodec对象:首先需要创建一个MediaCodec对象,并指定要进行的编解码操作(编码或解码)以及要使用的编解码器类型。
  2. 配置MediaFormat:接下来需要配置MediaFormat,即指定要解码的媒体数据的格式,包括媒体类型(音频或视频)、采样率、比特率等参数。
  3. 配置Surface(可选):如果是视频解码,可以通过设置Surface来将解码后的视频数据直接渲染到Surface上,以实现视频播放。
  4. 启动MediaCodec:配置完成后,可以调用start()方法启动MediaCodec。
  5. 输入数据:接下来需要将要解码的媒体数据传递给MediaCodec进行解码。可以通过调用queueInputBuffer()方法将媒体数据传递给MediaCodec。
  6. 获取解码数据:MediaCodec会将解码后的数据输出到指定的Surface或ByteBuffer中,可以通过调用dequeueOutputBuffer()方法获取解码后的数据。
  7. 渲染(可选):如果是视频解码并且使用了Surface,解码后的视频数据会直接渲染到Surface上,如果是音频解码或者视频解码但不使用Surface,需要将解码后的数据进行渲染或播放。
  8. 释放资源:解码完成后,需要释放MediaCodec对象及相关资源。

MediaCodec解码的过程包括配置、启动、输入数据、获取解码数据和渲染等步骤,通过这些步骤可以实现高效的音视频解码。

播放视频

使用MediaCodec解码本地h264文件并播放视频。

  1. 创建一个MediaExtractor来读取h264文件的数据流。
  2. 通过MediaFormat获取视频文件的格式信息,包括视频的宽、高、帧率等参数。
  3. 创建一个MediaCodec来进行视频解码。
  4. 将解码后的视频帧渲染到Surface上进行播放。
// 创建MediaExtractor并指定要解码的文件路径
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(path);

// 获取视频文件的格式信息
MediaFormat format = null;
for (int i = 0; i < extractor.getTrackCount(); i++) {
    format = extractor.getTrackFormat(i);
    String mime = format.getString(MediaFormat.KEY_MIME);
    if (mime.startsWith("video/")) {
        extractor.selectTrack(i);
        break;
    }
}

// 创建MediaCodec并配置解码器
MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
codec.configure(format, surface, null, 0);
codec.start();

// 读取并解码视频帧
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
boolean isEOS = false;
while (!isEOS) {
    int inputBufferIndex = codec.dequeueInputBuffer(10000);
    if (inputBufferIndex >= 0) {
        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
        int sampleSize = extractor.readSampleData(inputBuffer, 0);
        if (sampleSize < 0) {
            codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            isEOS = true;
        } else {
            codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
            extractor.advance();
        }
    }

    int outputBufferIndex = codec.dequeueOutputBuffer(info, 10000);
    if (outputBufferIndex >= 0) {
        codec.releaseOutputBuffer(outputBufferIndex, true);
    }
}

// 释放资源
codec.stop();
codec.release();
extractor.release();

具体实现:

// 解码工具类

public class H264Player implements Runnable {

    // 本地 h264 文件路径
    private String path;
    private Surface surface;
    private MediaCodec mediaCodec;
    private Context context;

    public H264Player(Context context, String path, Surface surface) {

        this.context = context;
        this.path = path;
        this.surface = surface;
        try {
            this.mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            // 视频宽高暂时写死
            MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 368, 384);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
            mediaCodec.configure(mediaFormat, surface, null, 0);
        } catch (IOException e) {
            // 解码芯片不支持,走软解
            e.printStackTrace();
        }
    }


    public void play() {
        mediaCodec.start();
        new Thread(this::run).start();
    }

    @Override
    public void run() {
        // 解码 h264
        decodeH264();
    }

    private void decodeH264() {
        byte[] bytes = null;
        try {
            bytes = getBytes(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 获取队列
        ByteBuffer[] byteBuffers = mediaCodec.getInputBuffers();
        int startIndex = 0;
        int nextFrameStart;
        int totalCount = bytes.length;

        while (true) {
            if (startIndex >= totalCount) {
                break;
            }
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            nextFrameStart = findFrame(bytes, startIndex+1,totalCount);
            // 往 ByteBuffer 中塞入数据
            int index = mediaCodec.dequeueInputBuffer(10 * 1000);
            Log.e("index",index+"");
            // 获取 dsp 成功
            if (index >= 0) {
                // 拿到可用的 ByteBuffer
                ByteBuffer byteBuffer = byteBuffers[index];
                byteBuffer.clear();
                byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);
                // 识别分隔符,找到分隔符对应的索引
                mediaCodec.queueInputBuffer(index, 0, nextFrameStart - startIndex, 0, 0);
                startIndex = nextFrameStart;

            }else {
                continue;
            }


            // 从 ByteBuffer 中获取解码好的数据
            int outIndex = mediaCodec.dequeueOutputBuffer(info,10 * 1000);
            if (outIndex > 0){
                try {
                    Thread.sleep(33);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mediaCodec.releaseOutputBuffer(outIndex, true);
            }

        }
    }

    private int findFrame(byte[] bytes, int startIndex, int totalSize) {
        for (int i = startIndex; i < totalSize - 4; i++) {
            if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {
                return i;
            }

        }
        return -1;
    }


    /**
     * 一次性读取文件
     *
     * @param path
     * @return
     * @throws IOException
     */
    public byte[] getBytes(String path) throws IOException {
        InputStream is = new DataInputStream(new FileInputStream(new File(path)));
        int len;
        int size = 1024;
        byte[] buf;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        buf = new byte[size];
        while ((len = is.read(buf, 0, size)) != -1)
            bos.write(buf, 0, len);
        buf = bos.toByteArray();
        return buf;
    }
    
    public void destroy(){
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
        }
    }
}

播放视频:

public class MainActivity extends AppCompatActivity {

    private H264Player h264Player;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkPermission();
        initSurface();
    }

    private void initSurface() {
        SurfaceView surfaceView = findViewById(R.id.surface);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                h264Player = new H264Player(MainActivity.this, new File(Environment.getExternalStorageDirectory(), "test.h264").getAbsolutePath(), surfaceHolder.getSurface());
                h264Player.play();
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            }
        });
    }

    private boolean checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE
            }, 1);
        }
        return false;
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 停止解码器
        h264Player.destroy();
    }
}

上述代码使用MediaCodec来解码视频流,并将解码后的视频渲染到SurfaceView上,在Activity销毁时释放MediaCodec资源。

原文地址:https://mp.weixin.qq.com/s/tiq1aCacgQX2CEEgTohRmw

延伸 · 阅读

精彩推荐
  • AndroidAndroid后台线程和UI线程通讯实例

    Android后台线程和UI线程通讯实例

    这篇文章主要介绍了Android后台线程和UI线程通讯实例,每一步的要点和步骤都有提及,并配有代码例子,需要的朋友可以参考下...

    冰冻鱼4652021-03-03
  • Androidandroid中实现指针滑动的动态效果方法

    android中实现指针滑动的动态效果方法

    本次实现的是类似于墨迹天气中轨迹图片上指针随着数值滚动滑动的效果,基本思路是开启线程,控制指针所在的imageview控件的padding属性。...

    Android开发网10782021-01-08
  • AndroidAndroid传递Bitmap对象在两个Activity之间

    Android传递Bitmap对象在两个Activity之间

    这篇文章主要介绍了Android传递Bitmap对象在两个Activity之间的相关资料,需要的朋友可以参考下...

    gloomyfish7252021-05-10
  • AndroidAndroid中Service(后台服务)详解

    Android中Service(后台服务)详解

    这篇文章主要介绍了Android中Service(后台服务)详解,本文讲解了Service的概念、作用、生命周期、启动方式和代码实例等内容,需要的朋友可以参考下...

    Android开发网8652021-03-25
  • AndroidAndroid多渠道打包总结(推荐)

    Android多渠道打包总结(推荐)

    多渠道打包一般应用于向不同应用市场提交app后用来统计不同渠道下载量等一些信息,这篇文章主要介绍了Android多渠道打包总结,非常具有实用价值,需要...

    墨龙龙龙3982022-08-09
  • AndroidAndroid编程实现二级下拉菜单及快速搜索的方法

    Android编程实现二级下拉菜单及快速搜索的方法

    这篇文章主要介绍了Android编程实现二级下拉菜单及快速搜索的方法,以实例形式较为详细的分析了Android实现二级下拉菜单及快速搜索的布局与功能实现技巧...

    越冬越酷6062021-04-12
  • AndroidImageView的属性android:scaleType的作用分析

    ImageView的属性android:scaleType的作用分析

    本篇文章是对ImageView的属性android:scaleType的作用进行了详细的分析介绍,需要的朋友参考下...

    Android开发网11912021-01-28
  • AndroidAndroid面试题问答整理

    Android面试题问答整理

    今天小编就为大家分享一篇关于Android面试题问答整理,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    pigdreams11332022-09-06