【Android】MediaCodec学习

news2025/2/24 5:11:02

在开源Android屏幕投屏代码scrcpy中,使用了MediaCodec去获取和display关联的surface的内容,再通过写fd的方式(socket等)传给PC端,

MediaCodec的处理看起来比较清楚,数据in和数据out

这里我们做另外一个尝试,读取手机中的mp4文件,显示到app的surface上,来学习MediaCodec的使用。

code

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;

public class PlayActivity2 extends AppCompatActivity implements SurfaceHolder.Callback {
    private static final int REQUEST_PERMISSION = 1;
    private static final String SAMPLE_MP4_FILE = "/sdcard/Download/test.mp4";
    private SurfaceView surfaceView;
    private MediaExtractor mediaExtractor;
    private MediaCodec mediaCodec;
    private boolean isPlaying = false;
    private String TAG = "testPlay";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new);

        Log.i(TAG, "onCreate");
        surfaceView = findViewById(R.id.surfaceView);
        surfaceView.getHolder().addCallback(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i(TAG, "onResume");
        if (!isPlaying) {
            Log.i(TAG, "set isPlaying true");
            isPlaying = true;
            //        playVideo();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (isPlaying) {
            Log.i(TAG, "onPause");
            isPlaying = false;
            releaseMediaCodec();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isPlaying = true;
        Log.i(TAG, "surfaceCreated");

//需要另外启动一个线程去处理
        new Thread() {
            @Override
            public void run() {
                playVideo();
            }
        }.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "surfaceChanged");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed");
        releaseMediaCodec();
    }


    private void playVideo() {
        try {
            Log.i(TAG, "playVideo");
            mediaExtractor = new MediaExtractor();
            mediaExtractor.setDataSource(SAMPLE_MP4_FILE);

            int videoTrackIndex = getVideoTrackIndex();
            if (videoTrackIndex >= 0) {
                MediaFormat format = mediaExtractor.getTrackFormat(videoTrackIndex);
                String mimeType = format.getString(MediaFormat.KEY_MIME);
                mediaCodec = MediaCodec.createDecoderByType(mimeType);
                Surface surface = surfaceView.getHolder().getSurface();
                mediaCodec.configure(format, surface, null, 0);
                mediaCodec.start();
                Log.i(TAG, "mediaCodec.start");
                decodeFrames(videoTrackIndex);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private int getVideoTrackIndex() {
        for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
            MediaFormat format = mediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("video/")) {
                mediaExtractor.selectTrack(i);
                return i;
            }
        }
        return -1;
    }

    private void decodeFrames(int videoTrackIndex) {
        boolean isEOS = false;
        final int TIMEOUT_US = 10000;

        while (!Thread.interrupted()) {
            if (!isPlaying)
                break;
            Log.i(TAG, "decodeFrames=, isPlaying=" + isPlaying);
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_US);
            Log.i(TAG, "inputBufferIndex=" + inputBufferIndex);
            if (inputBufferIndex >= 0) {
                int sampleSize = mediaExtractor.readSampleData(mediaCodec.getInputBuffer(inputBufferIndex), 0);
                if (sampleSize < 0) {
                    isEOS = true;
                    sampleSize = 0;
                }
                long presentationTimeUs = mediaExtractor.getSampleTime();
                mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, isEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
                if (!isEOS) {
                    Log.i(TAG, "mediaExtractor.advance()=" + sampleSize);
                    mediaExtractor.advance();
                }
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
            Log.i(TAG, "outputBufferIndex======" + outputBufferIndex);
            if (outputBufferIndex >= 0) {
                mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Log.i(TAG, "inputBufferIndex=, break");
                    break;
                }
            }

            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }


        }
    }

    private void releaseMediaCodec() {
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec = null;
        }
        if (mediaExtractor != null) {
            mediaExtractor.release();
            mediaExtractor = null;
        }
    }
}

注意,这里的mp4文件放在了sdcard中,需要获取读取权限

public void requestPermission() {
    if (Build.VERSION.SDK_INT >= 30) {
        if (!Environment.isExternalStorageManager()) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
            startActivity(intent);
            return;
        }
    } else {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            if (PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
                requestPermissions(requestPermission, requestPermissionCode);
            }
        }
    }
}

activity_new.xml里定义一个SurfaceView

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".newActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

playVideo的处理需要在另外一个线程中执行,不能在主线程执行,不然只能显示停止的一个画面。
01-28 18:14:19.388 21442 21442 I testPlay: set isPlaying true
01-28 18:14:19.431 21442 21442 I testPlay: surfaceCreated
01-28 18:14:19.431 21442 21442 I testPlay: playVideo
01-28 18:14:19.479 21442 21442 I testPlay: mediaCodec.start
01-28 18:14:19.479 21442 21442 I testPlay: decodeFrames=, isPlaying=true
01-28 18:14:19.480 21442 21442 I testPlay: inputBufferIndex=2
01-28 18:14:19.483 21442 21442 I testPlay: mediaExtractor.advance()=85878
01-28 18:14:19.493 21442 21442 I testPlay: outputBufferIndex======-1
01-28 18:14:19.504 21442 21442 I testPlay: decodeFrames=, isPlaying=true
01-28 18:14:19.504 21442 21442 I testPlay: inputBufferIndex=3
01-28 18:14:19.507 21442 21442 I testPlay: mediaExtractor.advance()=3049

在上述代码中,视频帧是通过 MediaCodec 解码后,使用 Surface 对象在 SurfaceView 上进行渲染的。

以下代码片段展示了视频帧的渲染过程:

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
if (outputBufferIndex >= 0) {
    mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        break;
    }
}

在每次循环中,首先调用 dequeueOutputBuffer() 方法来获取可用的输出缓冲区的索引。如果返回的索引大于等于0,则说明有可用的输出缓冲区。

然后,通过调用 releaseOutputBuffer() 方法,将输出缓冲区的索引传递给 MediaCodec,通知它可以释放该缓冲区并将其渲染到指定的 Surface 上。

最后,检查 BufferInfo 的 flags 标志,如果标志中包含 BUFFER_FLAG_END_OF_STREAM,则说明已经解码并渲染完整个视频帧序列,可以退出循环。

在循环中不断解码和渲染视频帧,就可以在 SurfaceView 上实时显示视频内容。

参考资料

Android MediaCodec解析-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1416638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Blender教程(基础)-面的细分与删除、挤出选区-07

一、Blender之面的细分 新建一个立方体&#xff0c;在编辑模式下、选中一个面。 在选中的面上单击右键弹出细分选项&#xff0c;选择细分。 在选中细分后、会默认细分1次。修改细分次数在左下角 二、Blender之面的删除 选择中需要操作的面&#xff0c;在英文状态下按X键弹…

ELK日志解决方案

ELK日志解决方案 ELK套件日志系统应该是Elasticsearch使用最广泛的场景之一了&#xff0c;Elasticsearch支持海量数据的存储和查询&#xff0c;特别适合日志搜索场景。广泛使用的ELK套件(Elasticsearch、Logstash、Kibana)是日志系统最经典的案例&#xff0c;使用Logstash和Be…

机房环境动力监控系统:S275远程控制网关助力高效管理

现场问题 1、机房安全隐患 机房存在意外断电、温湿度过高过低、漏水断路等隐患&#xff0c;传统监测手段难以提前发现和预警。 2、机房远程运维困难 因环境改变、非授权活动、设备状态变化等引起的事故&#xff0c;难以满足机房远程运维的可靠管控要求。 3、机房改造成本高…

POJ No.1852 Ants

思路分析 “转向”问题 假设蚂蚁A与蚂蚁B相遇后转向&#xff0c;可以视作A&#xff0c;B交换位置&#xff0c;从而消除转向。 距离问题 最长距离&#xff1a;比较每只蚂蚁距两端的最大距离&#xff0c;取两端中最大值&#xff0c;取一组中最长距离的最大值。 最短距离&…

八种Flink任务告警方式

目录 一、Flink应用分析 1.1 Flink任务生命周期 1.2 Flink应用告警视角分析 二、监控告警方案说明 2.1 监控消息队中间件消费者偏移量 2.2 通过调度系统监控Flink任务运行状态 2.3 引入开源服务的SDK工具实现 2.4 调用FlinkRestApi实现任务监控告警 2.5 定时去查询目标…

跟着小德学C++之TOTP

嗨&#xff0c;大家好&#xff0c;我是出生在达纳苏斯的一名德鲁伊&#xff0c;我是要立志成为海贼王&#xff0c;啊不&#xff0c;是立志成为科学家的德鲁伊。最近&#xff0c;我发现我们所处的世界是一个虚拟的世界&#xff0c;并由此开始&#xff0c;我展开了对我们这个世界…

RandomQuestionPicker简单的随机抽题系统

一个简单的随机抽题系统&#xff0c;题库以文件的方式读入程序&#xff0c;功能是随机抽题并记录某题抽取次数。刚好有需要&#xff0c;给自己写了个&#xff0c;顺便开源。 没做UI界面。需要的同学自取即可。 使用时将questions.txt文件和src并列放到Project目录下&#xff…

Linux中并发程序设计(进程的创建和回收、exec函数使用、守护进程创建和使用、GDB的父、子进程代码的调试、线程的创建和参数传递)

进程的创建和回收 进程概念 概念 程序 存放在磁盘上的指令和数据的有序集合&#xff08;文件&#xff09; 静态的 进程 执行一个程序所分配的资源的总称 动态的进程和程序比较 注&#xff1a;进程是存在RAM中&#xff0c;程序是存放在ROM(flash)中的进程内容 BSS段&#xff…

RK3588平台开发系列讲解(视频篇)RKMedia框架

文章目录 一、 RKMedia框架介绍二、 RKMedia框架API三、 视频处理流程四、venc 测试案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢RKMedia是RK提供的一种多媒体处理方案,可实现音视频捕获、音视频输出、音视频编解码等功能。 一、 RKMedia框架介绍 功能: VI(输…

2024.1.28每日一题

LeetCode 水壶问题 365. 水壶问题 - 力扣&#xff08;LeetCode&#xff09; 题目描述 有两个水壶&#xff0c;容量分别为 jug1Capacity 和 jug2Capacity 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 targetCapacity 升。 如果可以得到 targetCapacity 升水…

CSS 之 图片九宫格变幻效果

一、简介 ​ 本篇博客用于讲解如何实现图片九宫格变幻的样式效果&#xff0c;将图片分为九块填充在33的的九宫格子元素中&#xff0c;并结合grid、hover、transition等CSS属性&#xff0c;实现元素hover时&#xff0c;九宫格子元素合并为一张完整图片的动画效果。 ​ 为了简化…

嵌入式——实时时钟(RTC)

目录 一、初识RTC 1.简介 2.特性 3.后备寄存器和RTC寄存器特性 二、RTC组成 1.相关寄存器 &#xff08;1&#xff09;控制寄存器高位&#xff08;RTC_CRH&#xff09; &#xff08;2&#xff09;控制寄存器低位&#xff08;RTC_CRL&#xff09; &#xff08;3&#xf…

【Linux】分区向左扩容的方法

文章目录 为什么是向左扩容操作前的备份方法&#xff1a;启动盘试用Ubuntu后进行操作 为什么是向左扩容 Linux向右扩容非常简单&#xff0c;无论是系统自带的disks工具还是apt安装的gparted工具&#xff0c;都有图像化的界面可以操作。但是&#xff0c;都不支持向左扩容。笔者…

从 React 到 Qwik:开启高效前端开发的新篇章

1. Qwik Qwik 是一个为构建高性能的 Web 应用程序而设计的前端 JavaScript 框架,它专注于提供即时启动性能,即使是在移动设备上。Qwik 的关键特性是它采用了称为“恢复性”的技术,该技术消除了传统前端框架中常见的 hydration 过程。 恢复性是一种序列化和恢复应用程序状态…

PyTorch深度学习实战(33)——条件生成对抗网络(Conditional Generative Adversarial Network, CGAN)

PyTorch深度学习实战&#xff08;33&#xff09;——条件生成对抗网络 0. 前言1. 条件生成对抗网络1.1 模型介绍1.2 模型与数据集分析 2. 实现条件生成对抗网络小结系列链接 0. 前言 条件生成对抗网络 (Conditional Generative Adversarial Network, CGAN) 是一种生成对抗网络…

C#,最小生成树(MST)普里姆(Prim)算法的源代码

Vojtěch Jarnk 一、Prim算法简史 Prim算法&#xff08;普里姆算法&#xff09;&#xff0c;是1930年捷克数学家算法沃伊捷赫亚尔尼克&#xff08;Vojtěch Jarnk&#xff09;最早设计&#xff1b; 1957年&#xff0c;由美国计算机科学家罗伯特普里姆独立实现&#xff1b; 19…

Spring Boot 项目配置文件

文章目录 配置文件的作用properties基本语法读取文件信息缺点 yml基本语法优点配置不同数据类型字符串类型的写法 配置对象配置集合 读取配置文件的几种方法EnvironmentPropertySource使用原生方式读取 设置不同环境的配置文件 配置文件的作用 整个项目中重要的数据都是在配置…

2000-2022年上市公司全要素生产率测算数据OLS法(含原始数据+测算代码do文档+计算结果)

2000-2022年上市公司全要素生产率测算数据OLS法&#xff08;含原始数据测算代码do文档计算结果&#xff09; 1、时间&#xff1a;2000-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、统计截止日期、固定资产净额、year、股票简称、报表类型编…

【Axure教程0基础入门】00Axure9汉化版下载、安装、汉化、注册+01制作线框图

写在前面&#xff1a;在哔哩哔哩上面找到的Axure自学教程0基础入门课程&#xff0c;播放量最高&#xff0c;5个多小时。课程主要分为4个部分&#xff0c;快速入门、动态面板、常用动效、项目设计。UP主账号【Song老师产品经理课堂】。做个有素质的白嫖er&#xff0c;一键三连必…

【Spark系列3】RDD源码解析实战

本文主要讲 1、什么是RDD 2、RDD是如何从数据中构建 一、什么是RDD&#xff1f; RDD&#xff1a;弹性分布式数据集&#xff0c;Resillient Distributed Dataset的缩写。 个人理解&#xff1a;RDD是一个容错的、并行的数据结构&#xff0c;可以让用户显式的将数据存储到磁盘…