【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";@Overrideprotected 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);}@Overrideprotected void onResume() {super.onResume();Log.i(TAG, "onResume");if (!isPlaying) {Log.i(TAG, "set isPlaying true");isPlaying = true;//        playVideo();}}@Overrideprotected void onPause() {super.onPause();if (isPlaying) {Log.i(TAG, "onPause");isPlaying = false;releaseMediaCodec();}}@Overridepublic void surfaceCreated(SurfaceHolder holder) {isPlaying = true;Log.i(TAG, "surfaceCreated");//需要另外启动一个线程去处理new Thread() {@Overridepublic void run() {playVideo();}}.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.i(TAG, "surfaceChanged");}@Overridepublic 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"><SurfaceViewandroid: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.rhkb.cn/news/247275.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qml与C++的交互

qml端使用C对象类型、qml端调用C函数/c端调用qml端函数、qml端发信号-连接C端槽函数、C端发信号-连接qml端函数等。 代码资源下载&#xff1a; https://download.csdn.net/download/TianYanRen111/88779433 若无法下载&#xff0c;直接拷贝以下代码测试即可。 main.cpp #incl…

二叉树--199. 二叉树的右视图/medium 理解度C

199. 二叉树的右视图 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出…

FPGA 通过 UDP 以太网传输 JPEG 压缩图片

FPGA 通过 UDP 以太网传输 JPEG 压缩图片 简介 在 FPGA 上实现了 JPEG 压缩和 UDP 以太网传输。从摄像机的输入中获取单个灰度帧&#xff0c;使用 JPEG 标准对其进行压缩&#xff0c;然后通过UDP以太网将其传输到另一个设备&#xff08;例如计算机&#xff09;&#xff0c;所有…

Element-Plus如何实现表单校验和表单重置

一&#xff1a;页面布局介绍&#xff1a; 这是我刚刚用基于vue3element-plus写好的一个部门管理的页面 基本的增删改查已经写好&#xff0c;下面我只提供页面的template和style的代码&#xff1a; template <template><el-card class"box-card"><…

【YOLO系列算法俯视视角下舰船目标检测】

YOLO系列算法俯视视角下舰船目标检测 数据集和模型YOLO系列算法俯视视角下舰船目标检测YOLO系列算法俯视视角下舰船目标检测可视化结果 数据集和模型 数据和模型下载&#xff1a; YOLOv6俯视视角下舰船目标检测训练好的舰船目标检测模型舰船目标检测数据YOLOv7俯视视角下舰船…

贝锐蒲公英全新网页认证,保障企业访客无线网络安全

随着企业规模的不断扩大、人员的增长、无线终端数量/类型的增加&#xff0c;传统WiFi无线网络会暴露出越来越多的问题&#xff0c;导致无线网络管理困难。 比如&#xff1a;采用弱密码、安全防护不到位的默认设置、员工缺乏信息安全意识、未经授人员权访问无线网络…… 这些问…

【Redis】Redis有哪些适合的场景

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Redis ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 &#xff08;1&#xff09;会话缓存&#xff08;Session Cache&#xff09; &#xff08;2&#xff09;全页缓存&#xff08;FPC…

【极数系列】Flink配置参数如何获取?(06)

文章目录 gitee码云地址简介概述01 配置值来自.properties文件1.通过路径读取2.通过文件流读取3.通过IO流读取 02 配置值来自命令行03 配置来自系统属性04 注册以及使用全局变量05 Flink获取参数值Demo1.项目结构2.pom.xml文件如下3.配置文件4.项目主类5.运行查看相关日志 gite…

Linux 网络流量相关工具

本文聚焦于网络流量的查看、端口占用查看。至于网络设备的管理和配置&#xff0c;因为太过复杂且不同发行版有较大差异&#xff0c;这里就不赘述&#xff0c;后面看情况再写。 需要注意的是&#xff0c;这里列出的每一个工具都有丰富的功能&#xff0c;流量/端口信息查看只是其…

探索Pyecharts之美-绘制多彩旭日图的艺术与技巧【第37篇—python:旭日图】

文章目录 引言准备工作绘制基本旭日图调整颜色和样式添加交互功能定制标签和标签格式嵌套层级数据高级样式与自定义进阶主题&#xff1a;动态旭日图数据源扩展&#xff1a;外部JSON文件总结 引言 数据可视化在现代编程中扮演着重要的角色&#xff0c;而Pyecharts是Python中一个…

数据结构——链式二叉树(3)

本篇文章我们依然讲解链式二叉树的OJ题&#xff1b; 一、二叉树的层序遍历 层序遍历即从根节点开始一层一层的遍历。我们可以运用队列的先进先出特性实现&#xff01; //层序遍历 void a(BTNode* root) {Que qhead;Queueinit(&qhead);//先入队根节点if(root)QueuePush(&…

三维重建(7)--运动恢复结构SfM系统解析

目录 一、SfM系统&#xff08;两视图&#xff09; 1、特征提取 2、特征匹配 3、RANSAC求解基础矩阵F 4、完整的欧式结构恢复算法流程 二、基于增量法的SfM系统&#xff08;以OpenMVG为例&#xff09; 1、预处理 2、图像特征点提取与匹配 3、两视图重构点云 4、增加…

LPC系列一个定时器不同频率

1.背景 最近研究的LPC804里只有一个ctimer&#xff0c;很多时候用的捉襟见肘的&#xff0c;官方给了一份双匹配的参考例程&#xff0c;不过实际用处不大。不过我花了一晚上的时间&#xff0c;终于研究出来将一个定时器拆成四个定时器用的办法了。这个方法适用于用回调函数的LP…

RabbitMQ(一)

1、相关概念 1.1、消息队列&#xff08;MQ&#xff09; MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消…

移动Web——平面转换-多重转换

1、平面转换-多重转换 多重转换技巧&#xff1a;先平移再旋转 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&qu…

数据结构——链式二叉树

目录 &#x1f341;一、二叉树的遍历 &#x1f315;&#xff08;一&#xff09;、前序遍历(Preorder Traversal 亦称先序遍历) &#x1f315;&#xff08;二&#xff09;、中序遍历(Inorder Traversal) &#x1f315;&#xff08;三&#xff09;、后序遍历(Postorder Traver…

scrapy的入门使用

1 安装scrapy 命令: sudo apt-get install scrapy或者&#xff1a; pip/pip3 install scrapy2 scrapy项目开发流程 创建项目: scrapy startproject mySpider生成一个爬虫: scrapy genspider itcast itcast.cn提取数据:     根据网站结构在spider中实现数据采集相关内…

centos系统安装Ward服务器监控工具

简介 Ward是一个简约美观多系统支持的服务器监控面板 安装 1.首先安装jdk yum install java-1.8.0-openjdk-devel.x86_64 2.下载jar wget 3.启动 java -jar ward-1.8.8.jar 体验 浏览器输入 http://192.168.168.110:4000/ 设置服务名设置为:myserver 端口号:5000 点击…

WSL2 Debian系统添加支持SocketCAN

本人最近在使用WSL2&#xff0c;Linux系统选择的是Debian&#xff0c;用起来很不错&#xff0c;感觉可以代替VMware Player虚拟机。 但是WSL2 Debian默认不支持SocketCAN&#xff0c;这就有点坑了&#xff0c;由于本人经常要使用SocketCAN功能&#xff0c;所以决定让Debian支持…

开始学习第二十五天(番外)

今天分享一下写的小游戏啦 头文件game.h #include<stdio.h> #include<time.h> #include<stdlib.h> #define H 3 #define L 3 void InitBoard(char Board[H][L], int h, int l); void DisplayBoard(char Board[H][L], int h, int l); void playermove(cha…