【Android】MediaCodec学习

在开源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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SharedPreferences卡顿分析

SP的使用及存在的问题 SharedPreferences(以下简称SP)是Android本地存储的一种方式&#xff0c;是以key-value的形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里&#xff0c;SP的使用示例及源码解析参见&#xff1a;Android本地存储之SharedPreferences源码解析。以…

ASP .NET Core Api 使用过滤器

过滤器说明 过滤器与中间件很相似&#xff0c;过滤器&#xff08;Filters&#xff09;可在管道&#xff08;pipeline&#xff09;特定阶段&#xff08;particular stage&#xff09;前后执行操作。可以将过滤器视为拦截器&#xff08;interceptors&#xff09;。 过滤器级别范围…

第二证券:外围突传大利好!看涨期权交易骤增,中国资产大反攻继续?

外资正在做多我国财物。 据海外买卖网站marketchameleon的最新数据显现&#xff0c;海外挂钩追寻我国股票的iShares我国大型股ETF(FXI)的看涨期权买卖量在近一周内出现骤增&#xff0c;到达一年多来的最高点。别的&#xff0c;专心于科技范畴的KraneShares CSI我国互联网ETF&a…

开源之力与GPT的碰撞:探索未来技术的无限可能

摘要&#xff1a; 在本文中&#xff0c;我们将探讨开源软件与GPT&#xff08;大型预训练语言模型&#xff09;的完美结合如何推动技术的飞速发展。我们将简要介绍开源文化的价值观及其对技术创新的影响&#xff0c;分析GPT系列模型在开源社区中的发展与应用&#xff0c;并通过代…

STM32控制DS18B20温度传感器获取温度

时间记录&#xff1a;2024/1/28 一、DS18B20温度传感器介绍 &#xff08;1&#xff09;测温范围-55℃~125℃&#xff0c;在-10℃到85℃范围内误差为0.4 &#xff08;2&#xff09;返回的温度数据为16位二进制数据 &#xff08;3&#xff09;STM32和DS18B20通信使用单总线协议…

Linux下安装Nginx及配置SSL证书

安装 Nginx nginx 的一些模块需要依赖一些 lib 库&#xff0c;在安装 nginx 之前&#xff0c;须先安装这些 lib 库&#xff0c;比如常见依赖库主要有g、gcc、openssl-devel、pcre-devel和zlib-devel 所以执行如下命令安装&#xff1a; $ yum install gcc-c pcre pcre-devel z…

node后端服务框架

前言&#xff1a; 随着互联网的快速发展&#xff0c;Web 开发已成为当今计算机领域中非常重要的一部分。 Node.js 的出现为前端开发者提供了一个全新的方向&#xff0c;使得他们可以同时涉足前端和后端开发。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff…

批量导出域控用户及其所在OU和组

在Windows域环境中&#xff0c;批量导出域控用户及其所在OU&#xff08;组织单位&#xff09;和组成员身份信息&#xff0c;可以使用PowerShell脚本实现。以下是一个基本的示例脚本&#xff1a; Import-Module ActiveDirectory# 遍历所有用户 Get-ADUser -Filter * -Propertie…

数组奇缘:林浩然与杨凌芸的Java编程冒险记

数组奇缘&#xff1a;林浩然与杨凌芸的Java编程冒险记 Array Odyssey: The Java Programming Adventure of Lin Haoran and Yang Lingyun 在Java编程的广阔天地中&#xff0c;林浩然和杨凌芸的故事如同一段奇妙而生动的冒险传奇。林浩然&#xff0c;一个对代码充满热情、逻辑严…

python16-Python的字符串之转义字符

.前面已经提到&#xff0c;在字符串中可以使用反斜线进行转义;如果字符串本身包含反斜线&#xff0c;则需要使用“”表示&#xff0c;“W”就是转义字符。Python当然不会只支持这么几个转义字符&#xff0c;Python支持的转义字符如下表 掌握了上面的转义字符之后&#xff0c;下…

互斥锁/读写锁的概念及使用、死锁的避免

互斥锁的概念和使用 线程通信-互斥 临界资源&#xff1a;一次只允许一个任务(进程、线程)访问的共享资源 临界区&#xff1a;访问临界资源的代码 互斥机制&#xff1a;mutex互斥锁&#xff0c;任务访问临界资源前申请锁&#xff0c;访问完后释放锁互斥锁初始化 两种方法创建互…

中科星图——MODIS/006/MYD13A1的MYD13A1.006类数据集

数据名称&#xff1a; MYD13A1.006 Modis 16天 Aqua 500m 数据来源&#xff1a; NASA 时空范围&#xff1a; 2000-2022年 空间范围&#xff1a; 全国 数据简介&#xff1a; MOD13A1 V6数据集是由Aqua星搭载的中分辨率成像光谱仪获取的L3级植被指数产品&#xff0c…

三角函数转换(积分必备)

目录 一、诱导公式 二、二角和差公式 三、积化和差公式 四、万能、辅助角公式 五、倍角公式 六、反三角函数 七、余弦定理 一、诱导公式 1.公式一&#xff1a;设α为任意角&#xff0c;πα的三角函数值与α的三角函数值之间的关系 sin(π A) &#xff1d;&#xff0d;s…

《向量数据库指南》——Milvus Cloud向量过滤搜索及其优化

向量过滤搜索是一种基于条件的向量搜索方法&#xff0c;常用于推荐系统和信息检索等领域&#xff0c;能够帮助用户快速找到在给定条件下与其查询相关的内容。 在 Milvus Cloud社区中&#xff0c;这也是呼声比较高的功能。为满足广大用户的需求&#xff0c;Milvus Cloud在 Knowh…

王殿华主任:中医对睡眠的认知与睡眠障碍分类

睡眠不足不仅会导致黑眼圈&#xff0c;还会带来许多健康风险。怎样才能睡得更好&#xff1f; 一、睡眠障碍的分类 失眠症呼吸系统相关睡眠障碍&#xff1b;中枢多发性睡眠&#xff1b;睡眠节律紊乱&#xff1b;睡眠异常&#xff1b;睡眠运动障碍&#xff1b;其他睡眠障碍。 失…

CSS3的学习笔记

CSS3的学习笔记 什么是css: CSS是层叠样式表&#xff08;Cascading Style Sheets&#xff09;的缩写&#xff0c;是一种用来描述网页样式和布局的标记语言。它可以控制网页中的文字大小、颜色、间距、背景、边框、布局等方面&#xff0c;使网页更加美观和易于阅读。通过CSS&a…

Jmeter学习系列之一:Jmeter的详细介绍

目录 一、Jmeter的介绍 二、Jemeter的特点 三、Jemter相关概念 3.1采样器&#xff08;Samplers&#xff09; 3.2逻辑控制器&#xff08;Logic Controllers&#xff09; 3.3监听器&#xff08;Listeners&#xff09; 3.4配置元件&#xff08;Configuration Elements&#…

python 匿名函数lambda的简洁用法

当前版本&#xff1a; Python 3.8.4 文章目录如下 1. lambda 的特点 2. lambda 的用法 2.1. 基本语法 2.2. 函数传参 2.3. 结合条件语句 3. lambda 的应用场景 3.1. 处理列表 3.2. 处理字典 1. lambda 的特点 lambda 是一种匿名函数的定义方式&#xff0c;也称为 lam…

Windows 7 x64 SP1 安装 Google Chrome 109.0.5414.120 (正式版本) (64 位)

1 使用 IE 浏览器 输入网址 Google Chrome 网络浏览器得益于 Google 智能工具&#xff0c;Chrome 现在更易用、更安全、更快速。https://www.google.cn/chrome/&#xff0c;点击下载 Chrome。 2 点击 接受并安装。 3 提示。 4 保存。 5 双击 运行 ChromeSetup.exe。 6 等待安…

MySQL十部曲之六:数据操作语句(DML)

文章目录 前言语法约定DELETEINSERTSELECT查询列表SELECT 选项子句FROMWHEREORDER BYGROUP BYHAVINGWINDOWLIMITFOR SELECT ... INTO连接查询CROSS JOIN和INNER JOINON和USINGOUTER JOINNATURE JOIN 子查询标量子查询使用子查询进行比较带有ANY、IN或SOME的子查询带有ALL的子查…