Android APP 音视频(01)MediaCodec解码H264码流

news2024/11/24 7:40:31

说明: 此MediaCodec解码H264实操主要针对Android12.0系统。通过读取sd卡上的H264码流Me获取视频数据,将数据通过mediacodec解码输出到surfaceview上。


1 H264码流和MediaCodec解码简介

1.1 H264码流简介

H.264,也被称为MPEG-4 AVC(Advanced Video Coding),是一种广泛使用的数字视频压缩标准,主要用于视频编码。H.264标准由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)共同开发,旨在提供比之前的视频编码标准更高的数据压缩效率。

H.264是一种基于块的编码技术,它将视频帧分为多个宏块(Macroblocks,MBs),每个宏块包含亮度信息和色度信息。

关于H264码流相关概念还有:

帧类型,包括I、P、B三种类型,说明如下:

  • I帧(Intra-coded frames):关键帧,不依赖其他帧进行解码,包含完整的图像信息。
  • P帧(Predictive-coded frames):预测帧,依赖前一个I帧或P帧进行解码,包含相对于前一帧的差分信息。
  • B帧(Bidirectional predictive-coded frames):双向预测帧,依赖前后两个帧进行解码,用于提高压缩效率。

编码过程:包括帧内预测(Intra prediction)、帧间预测(Inter prediction)、变换(Transform)、量化(Quantization)和熵编码(Entropy coding)等步骤。

码流结构:H.264码流由一系列的NAL单元(Network Abstraction Layer Units)组成,每个NAL单元包含一个头部和数据负载,头部定义了负载的类型和重要性。

等等概念,想要有更多了解,可查看以下文章,持续更新中:

系统化学习 H264视频编码(01)基础概念

 

系统化学习 H264视频编码(02) I帧 P帧 B帧 引入及相关概念解读

系统化学习 H264视频编码(03)数据压缩流程及相关概念

。。。

1.2 MediaCodec解码说明

MediaCodec 是 Android 提供的一个音视频编解码器类,允许应用程序对音频和视频数据进行编码(压缩)和解码(解压缩)。它在 Android 4.1(API 级别 16)版本中引入,广泛应用于处理音视频数据,如播放视频、录制音频等。

以下是 MediaCodec 解码的基本步骤:

  1. 创建 MediaCodec 实例:通过调用 MediaCodec.createDecoderByType 方法并传入解码类型(如 "video/avc" 或 "audio/mp4a-latm")来创建解码器。

  2. 配置解码参数:通过调用 configure 方法配置解码器,传入解码参数如解码格式、输出格式等。

  3. 准备输出 Surface:为解码器准备输出 Surface。输出 Surface 用于接收解码后的数据,并显示在屏幕上。

  4. 开始解码:调用 start 方法启动解码器。

  5. 发送输入数据:将待解码的数据通过 write 方法发送到解码器的输入队列。

  6. 处理输出数据:监听输出队列,通过 dequeueOutputBuffer 方法获取解码后的数据,并将其显示在屏幕上。

  7. 停止解码:解码完成后,调用 stop 方法停止解码器。

  8. 释放资源:调用 release 方法释放解码器资源。

通过这些步骤,应用程序可以实现对视频和音频数据的高效编解码处理。针对本工程,主要通过从sd卡上读取h264码流,通过mediacodec解码视频并播放到surfaceview上。

2 MediaCodec解码H264码流代码完整解读(android Q)

2.1 关于权限部分的处理

关于权限,需要在AndroidManifest.xml中添加权限,具体如下所示:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

关于运行时权限的请求等,这里给出一个工具类参考代码,具体如下所示:

public class Permission {
    public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 1;
    //需要申请权限的数组
    private static final String[] permissions = {
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
    };
    //保存真正需要去申请的权限
    private static final List<String> permissionList = new ArrayList<>();

    public static int RequestCode = 100;

    public static void requestManageExternalStoragePermission(Context context, Activity activity) {
        if (!Environment.isExternalStorageManager()) {
            showManageExternalStorageDialog(activity);
        }
    }

    private static void showManageExternalStorageDialog(Activity activity) {
        AlertDialog dialog = new AlertDialog.Builder(activity)
                .setTitle("权限请求")
                .setMessage("请开启文件访问权限,否则应用将无法正常使用。")
                .setNegativeButton("取消", null)
                .setPositiveButton("确定", (dialogInterface, i) -> {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                    activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE);
                })
                .create();
        dialog.show();
    }

    public static void checkPermissions(Activity activity) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permission);
            }
        }

        if (!permissionList.isEmpty()) {
            requestPermission(activity);
        }
    }

    public static void requestPermission(Activity activity) {
        ActivityCompat.requestPermissions(activity,permissionList.toArray(new String[0]),RequestCode);
    }
}

这样,如果后面又更多的权限,都可以使用该方法来处理,处理方式为:

Permission.checkPermissions(this);
Permission.requestManageExternalStoragePermission(getApplicationContext(), this);

2.2 解码的处理

关于解码部分,主要是MediaCodec的初始化、解码处理部分,代码如下所示:

public class H264Decoder implements  Runnable {
    private final String path;
    private final String TAG = "H264Decoder";
    MediaCodec mediaCodec;
    boolean enablePlay = false;

    public H264Decoder(String path, Surface surface, int width , int height) {
        this.path = path;
        try {
            mediaCodec = MediaCodec.createDecoderByType("video/avc");
            MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", width, height);
            mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
            mediaCodec.configure(mediaformat, surface, null, 0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void play() {
        enablePlay = true;
        mediaCodec.start();
        new Thread(this).start();
    }

    public void stop(){
        enablePlay = false;
    }
    @Override
    public void run() {
        try {
            byte[] bytes = null;
            try {
                //注意:这里是从文件中一次性读H264取码流数据,因此不适合特别大的视频
                bytes = getBytes(path);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            int startIndex = 0;
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            while (enablePlay) {
                int nextFrameStart = findByFrame(bytes, startIndex+5, bytes.length);
                //MediaCodec输入缓冲区操作
                int inIndex =  mediaCodec.dequeueInputBuffer(10000);
                if (inIndex >= 0) {
                    ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inIndex);
                    int length = nextFrameStart - startIndex;
                    byteBuffer.put(bytes, startIndex, length);
                    mediaCodec.queueInputBuffer(inIndex, 0, length, 0, 0);
                    startIndex = nextFrameStart;
                }

                //MediaCodec输出缓冲区操作
                int outIndex =mediaCodec.dequeueOutputBuffer(info,10000);
                if (outIndex >= 0) {
                    try {
                        //这里延迟下,避免刷的过快
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    mediaCodec.releaseOutputBuffer(outIndex, true);
                }
            }
        } catch (Exception e) {
            Log.i(TAG, "run decoder error:"+e.toString());
        }
    }

    private int findByFrame( byte[] bytes, int start, int totalSize) {
        for (int i = start; i <= totalSize-4; i++) {
            //这里是一帧的结束符 00 00 00 01 或者 00 00 01
            if (((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x00) && (bytes[i + 3] == 0x01))
            ||((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x01))) {
                return i;
            }
        }
        return -1;
    }

    public byte[] getBytes(String path) throws IOException {
        InputStream is = new DataInputStream(Files.newInputStream(new File(path).toPath()));
        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;
    }
}

2.3 主流程代码参考实现

这里以 H264decoderActivity 为例,给出一个MediaCodec解码功能代码的参考实现。具体实现如下:

public class H264decoderActivity extends AppCompatActivity {
    H264Decoder h264Decoder;
    private final String TAG = "MainActivity";
    Context mContext;
    Surface surface;
    private boolean isPlaying = false; // 用于跟踪播放状态

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        mContext = this;
        setContentView(R.layout.h264_decode_activity_main);

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        initSurface();
        Permission.checkPermissions(this);
        Permission.requestManageExternalStoragePermission(getApplicationContext(), this);
        Button playButton = findViewById(R.id.button);
        playButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 切换播放状态
                isPlaying = !isPlaying;
                // 根据播放状态更新按钮文本
                if (isPlaying) {
                    playButton.setText(R.string.stopplay);
                    //Environment.DIRECTORY_DOWNLOADS), "ags/out.h264").getAbsolutePath(),
                    h264Decoder = new H264Decoder(
                            new File(Environment.getExternalStoragePublicDirectory(
                                    Environment.DIRECTORY_DOWNLOADS), "ags/outputtest4.h264").getAbsolutePath(),
                            surface,1280,720);
                    h264Decoder.play();
                } else {
                    playButton.setText(R.string.startplay);
                    h264Decoder.stop();
                }
            }
        });
    }

    private void initSurface() {
        SurfaceView mSurface = findViewById(R.id.preview);
        mSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                Log.d(TAG,"surfaceCreated");
                surface=surfaceHolder.getSurface();
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                Log.d(TAG,"surfaceChanged");
            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
                Log.d(TAG,"surfaceDestroyed");
            }
        });
    }
}

这里涉及的layout布局文件内容如下:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <SurfaceView
        android:id="@+id/preview"
        android:layout_width="372dp"
        android:layout_height="240dp"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/playtest"
        app:layout_constraintTop_toBottomOf="@id/preview"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.4 解码 demo实现效果

这里是找一个mp4格式的测试视频,使用ffmpeg将mp4格式中的视频码流输出出来。使用命令为:

$ffmpeg -i inputtest.mp4 -vcodec libx264 -preset slow -b:v 2000k -crf 21 out.h264

将其push到sd卡上,完整路径为:/sdcard/Download/ags/outputtest4.h264。实际运行效果展示如下:

446068457f4e42d5ac6720ec2279d858.png

 

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

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

相关文章

用51单片机或者stm32能否开发机器人呢?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;能的。但是由于单片机和st…

记录安装android studio踩的坑 win7系统

最近在一台新电脑上安装android studio,报了很多错误&#xff0c;也是费了大劲才解决&#xff0c;发出来大家一起避免一些问题&#xff0c;找到解决方法。 安装时一定要先安装jdk&#xff0c;cmd命令行用java -version查当前的版本&#xff0c;没有的话&#xff0c;先安装jdk,g…

ARP欺骗——华为ensp

首先&#xff0c;搭建好网络拓扑。网络设备包含客户端Client1和服务端Server1&#xff0c;交换机 以及 云。 图中的 Client和Server 配置IP地址&#xff0c;要和 vm8 在相同的网段。故设置客户端ip为192.168.11.10 &#xff0c;服务端ip为&#xff1a;192.168.11.20&#xff0…

MySQL补充性文件

数据库专属单词 authentication #身份验证 delimiter #分隔符 character #字符集 collate #整理。 指定字符集的排序规则 unicode #统一码 flush #刷新 privileges #特权 string #串 set #设置 use #使用 zerofill #修饰符。0可以填补输出的值 unsigned #修饰符。无符…

STM32--HAL库--定时器篇

一&#xff1a;如何配置定时器 打开对应工程串口配置好的工程&#xff08;上一篇博客&#xff09;做如下配置&#xff1a; 定时器的中断溢出时间计算公式是&#xff1a; 由图得T100*1000/100MHz 注&#xff1a;100MHz100000000 所以溢出时间等于1ms 关于上图4的自动重装…

Robot Operating System——初探动态配置Parameters

大纲 同步模式Node内使用declare_parameter方法声明Parameters创建Parameter同步访问客户端跨Node修改Parameters跨Node查询Parameters完整代码运行结果 异步模式创建Node&#xff0c;设置Parameters创建Parameter异步访问客户端异步设置&#xff0c;同步等待异步查询&#xff…

VMware三种网络模式---巨细

文章目录 目录 ‘一.网络模式概述 二.桥接模式 二.NAT模式 三.仅主机模式 四.案例演示 防火墙配置&#xff1a; 虚拟电脑配置 前言 本文主要介绍VMware的三种网络模式 ‘一.网络模式概述 VMware中分为三种网络模式&#xff1a; 桥接模式&#xff1a;默认与宿主机VMnet0绑…

CSP-J模拟赛day1——解析+答案

题目传送门 yjq的吉祥数 题解 送分题&#xff0c;暴力枚举即可 Code #include<bits/stdc.h> using namespace std;int l,r; int num1,tmp0,q[10000],a[10000]; int k (int x){for (int j1;j<tmp;j){if (xq[j])return 0;}return 1; } int main(){while (num<100…

php连接sql server

php连接sqlserver有三种方式 一&#xff1a;odbc连接&#xff0c;废话不多说直接上代码,封装了一个单例 <?php /*** odbcServer.php* Author: Erekys*/namespace App\Model; class odbcServer{public static $server;public static $username;public static $password;pu…

dora-rs学习之Rust 和机器人

最近在研究一个网红机器人框架 dora-rs&#xff0c;也看到一些具身智能相关&#xff0c;做机器人遥操作与数据采集及可视化系统的公司使用rust来开发&#xff0c;这里探讨一下dora-rs和rust给机器人带来什么&#xff0c;引述官方的描述&#xff1a;Hello from dora-rs | dora-r…

svelte - 5. 动画

svelte/motion模块导出两个函数&#xff1a; tweened 和 spring。 用于创建writable&#xff08;可写&#xff09;store&#xff0c;其值会在set 和 update之后更新&#xff0c;而不是立即更新。 &#xff08;人话&#xff1a;用 svelte 提供的tweened 和 spring可以达成流程变…

数学建模学习(1)遗传算法

一、简介 遗传算法&#xff08;Genetic Algorithm, GA&#xff09;是一种用于解决优化和搜索问题的进化算法。它基于自然选择和遗传学原理&#xff0c;通过模拟生物进化过程来寻找最优解。 以下是遗传算法的主要步骤和概念&#xff1a; 初始化种群&#xff08;Initialization&a…

react中嵌套路由以及默认显示二级路由

1.安装插件 npm install react-router-dom 2.新建文件及页面 在scr/page下新建layout、about、home文件夹&#xff0c;分别在对应的文件夹下新建入口文件index.js&#xff1b;src下新建router文件夹&#xff0c;该文件夹下新建入口文件index.js 3.配置路由 如何配置路由&am…

昇思学习打卡-25-自然语言处理/RNN实现情感分类

文章目录 数据下载加载预训练词向量数据集预处理模型构建损失函数与优化器训练逻辑评估指标和逻辑模型训练与保存模型加载与测试自定义输入测试测试 情感分类是自然语言处理中的经典任务&#xff0c;是典型的分类问题。本节使用MindSpore实现一个基于RNN网络的情感分类模型&…

android audio不同音频流,(六)settings内音频流音量调整

&#xff08;1&#xff09;settings内&#xff0c;可设置音频流音量&#xff0c;如下图&#xff1a; &#xff08;2&#xff09;settings调整音量条进度&#xff0c;会触发SeekBarVolumizer对象&#xff1a; SeekBarVolumizer文件路径&#xff1a; frameworks/base/core/java/…

设计模式13-单件模式

设计模式13-单件模式 写在前面对象性能模式典型模式1. 单例模式&#xff08;Singleton Pattern&#xff09;2. 享元模式&#xff08;Flyweight Pattern&#xff09;3. 原型模式&#xff08;Prototype Pattern&#xff09;4. 对象池模式&#xff08;Object Pool Pattern&#xf…

WebRTC QoS方法十三.2(Jitter延时的计算)

一、背景介绍 一些报文在网络传输中&#xff0c;会存在丢包重传和延时的情况。渲染时需要进行适当缓存&#xff0c;等待丢失被重传的报文或者正在路上传输的报文。 jitter延时计算是确认需要缓存的时间 另外&#xff0c;在检测到帧有重传情况时&#xff0c;也可适当在渲染时…

无人机图像目标检测技术详解

当前研究领域的热点之一。无人机搭载的高清摄像头能够实时捕获大量图像数据&#xff0c;对这些数据进行有效的目标检测对于军事侦察、环境监测、灾害救援等领域具有重要意义。本文将对无人机图像目标检测技术进行详解&#xff0c;包括图像处理技术、目标检测算法、关键技术应用…

LoFTR关键点特征匹配算法环境构建与图像匹配测试Demo

0&#xff0c;LoFTR CVPR 2021论文《LoFTR: Detector-Free Local Feature Matching with Transformers》开源代码 1&#xff0c;项目主页 LoFTR: Detector-Free Local Feature Matching with Transformers 2&#xff0c;GItHub主页 GitHub - zju3dv/LoFTR: Code for "…

Gen AI核心技术发展趋势分析

Gen AI核心技术解析及发展趋势 判别式模型&#xff0c;适用于处理回归与分类任务&#xff0c;其核心在于精准区分各类数据。与生成模型的生成新数据不同&#xff0c;判别模型专注于揭示输入特征与输出标签之间的紧密联系&#xff0c;从而实现准确分类或预测。在众多应用领域&am…