基于 SurfaceView、AudioTrack、MediaCodec 和 MediaExtractor 解码 MP4 播放

2023-05-16

一. 前言

上篇文章介绍了 基于Camera、AudioRecord 、MediaCodec 和 MediaMuxer 录制 MP4 , 录制的过程是这样的,那么相应的播放过程就是上述过程的逆过程,本篇文章将介绍如何通过 MediaExtractor 分离视频流和音频流,再通过 MediaCodec 解码,将数据传递给 SurfaceView 播放视频,给 AudioTrack 播放音频。

MediaExtractor

MediaExtractor 是 MediaMuxer 的逆过程,主要用于音视频混合数据的分离,并获取相应的音频轨对应的音频格式,和视频轨和视频格式。

1. 初始化
MediaExtractor extractor = new MediaExtractor();
2. 设置数据源
 extractor.setDataSource(...);
3. 找到音频轨或视频轨的媒体格式
 int numTracks = extractor.getTrackCount();
 for (int i = 0; i < numTracks; ++i) {
   MediaFormat format = extractor.getTrackFormat(i);
   String mime = format.getString(MediaFormat.KEY_MIME);
   if (weAreInterestedInThisTrack) {
     extractor.selectTrack(i);
   }
 }
4. 读取数据

 ByteBuffer inputBuffer = ByteBuffer.allocate(...)
 while (extractor.readSampleData(inputBuffer, ...) >= 0) {
   int trackIndex = extractor.getSampleTrackIndex();
   long presentationTimeUs = extractor.getSampleTime();// 获取 pts
   ...
   extractor.advance();//获取下一帧
 }

5. 释放资源

 extractor.release();
 extractor = null;

二. 解码播放 MP4

1. 音频处理
找到音频轨和媒体格式
MediaExtractor audioExtractor = new MediaExtractor();
        MediaCodec audioCodec = null;
        try {
            audioExtractor.setDataSource(mFileDescriptor);
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
            MediaFormat mediaFormat = audioExtractor.getTrackFormat(i);
            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mimeType.startsWith("audio/")) {
                audioExtractor.selectTrack(i);
                ...
根据 MediaFormat 创建 AudioTrack
for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
            MediaFormat mediaFormat = audioExtractor.getTrackFormat(i);
            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mimeType.startsWith("audio/")) {
                audioExtractor.selectTrack(i);
                int audioChannels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                int audioSampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                int minBufferSize = AudioTrack.getMinBufferSize(audioSampleRate, audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
                mAudioInputBufferSize = minBufferSize > 0 ? minBufferSize * 4 : maxInputSize;
                int frameSizeInBytes = audioChannels * 2;
                mAudioInputBufferSize = (mAudioInputBufferSize / frameSizeInBytes) * frameSizeInBytes;
                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                        44100,
                        (audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO),
                        AudioFormat.ENCODING_PCM_16BIT,
                        mAudioInputBufferSize,
                        AudioTrack.MODE_STREAM);
                mAudioTrack.play();
                
                ...

创建编解码器并开始解码
 //
                try {
                    audioCodec = MediaCodec.createDecoderByType(mimeType);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                audioCodec.configure(mediaFormat, null, null, 0);
                break;
            }
        }
        if (audioCodec == null) {
            return;
        }
        audioCodec.start();
        MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();
        while (mIsPlaying) {
            int inputIndex = audioCodec.dequeueInputBuffer(10_000);
            if (inputIndex < 0) {
                mIsPlaying = false;
            }
            ByteBuffer inputBuffer = audioCodec.getInputBuffer(inputIndex);
            inputBuffer.clear();
            int sampleSize = audioExtractor.readSampleData(inputBuffer, 0);
            if (sampleSize > 0) {
                audioCodec.queueInputBuffer(inputIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0);
                audioExtractor.advance();
            } else {
                mIsPlaying = false;
            }

            int outputIndex = audioCodec.dequeueOutputBuffer(decodeBufferInfo, 10_000);
            ByteBuffer outputBuffer;
            byte[] chunkPCM;
            while (outputIndex >= 0) {
                outputBuffer = audioCodec.getOutputBuffer(outputIndex);
                chunkPCM = new byte[decodeBufferInfo.size];
                outputBuffer.get(chunkPCM);
                outputBuffer.clear();
                mAudioTrack.write(chunkPCM, 0, decodeBufferInfo.size);
                audioCodec.releaseOutputBuffer(outputIndex, false);
                outputIndex = audioCodec.dequeueOutputBuffer(decodeBufferInfo, 10_000);

            }

        }
2. 视频处理
找到音视频轨并打开解码器
MediaExtractor videoExtractor = new MediaExtractor();
        MediaCodec videoCodec = null;
        long startWhen = 0;
        try {
            videoExtractor.setDataSource(mAssetFileDescriptor);
        } catch (IOException e) {
            e.printStackTrace();
        }
        boolean firstFrame = false;
        for (int i = 0; i < videoExtractor.getTrackCount(); i++) {
            MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);
            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mimeType.startsWith("video/")) {
                videoExtractor.selectTrack(i);
                try {
                    videoCodec = MediaCodec.createDecoderByType(mimeType);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                videoCodec.configure(mediaFormat, mSurface, null, 0);
                break;
            }
        }
        if (videoCodec == null) {
            return;
        }
        videoCodec.start();
解码,渲染数据化
while (!mEOF) {
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            ByteBuffer[] inputBuffers = videoCodec.getInputBuffers();
            int inputIndex = videoCodec.dequeueInputBuffer(10_000);
            if (inputIndex > 0) {
                ByteBuffer byteBuffer = inputBuffers[inputIndex];
                int sampleSize = videoExtractor.readSampleData(byteBuffer, 0);
                if (sampleSize > 0) {
                    videoCodec.queueInputBuffer(inputIndex, 0, sampleSize, videoExtractor.getSampleTime(), 0);
                    videoExtractor.advance();
                } else {
                    videoCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                }
            }

            int outputIndex = videoCodec.dequeueOutputBuffer(bufferInfo, 10_000);
            switch (outputIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    videoCodec.getOutputBuffers();
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    break;
                default:
                    if (!firstFrame) {
                        startWhen = System.currentTimeMillis();
                        firstFrame = true;
                    }
                    long sleepTime = (bufferInfo.presentationTimeUs / 1000) - (System.currentTimeMillis() - startWhen);
                    if (sleepTime > 0) {
                        SystemClock.sleep(sleepTime);
                    }
                    videoCodec.releaseOutputBuffer(outputIndex, true);
                    break;

            }
            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                mEOF = true;
                break;
            }
        }

流程图如图所示:
image.png
github Demo

欢迎关注我的微信公众号【海盗的指针】

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于 SurfaceView、AudioTrack、MediaCodec 和 MediaExtractor 解码 MP4 播放 的相关文章

  • 在贝塞尔曲线路径上移动对象

    我想在贝塞尔曲线路径上从上到下移动图像 但我不知道如何计算该路径的 x y 点和斜率 该路径如下图所示 我有起点 终点和两个控制点 Path path new Path Point s new Point 150 5 Point cp1 n
  • MFCreateFMPEG4MediaSink 不生成 MSE 兼容的 MP4

    我正在尝试将 H 264 视频源流式传输到网络浏览器 Media Foundation 用于编码分段的 MPEG4 流 MFCreateFMPEG4MediaSink with MFTranscodeContainerType FMPEG4
  • 在 Android AudioTrack 中使用缓冲区

    我试图弄清楚如何使用缓冲区音轨 http developer android com reference android media AudioTrack html有效地传输音乐 我知道您可以使用以下命令对音频进行排队write http
  • 如何在 Java Swing 应用程序中播放 MP4 视频

    有谁知道我可以在 JPanel 中播放 mp4 视频文件的方法吗 我尝试过使用 avi 文件使用 JMF 但没有成功 现在我对播放视频文件这样一个简单的任务变得如此乏味感到困惑和沮丧 请任何人告诉我我可以走什么路 我将不胜感激 我听说过 V
  • 在相机预览上叠加静态可绘制图像

    我需要在手机处于相机预览模式时显示静态 png 图像 目前我正在引用此链接 a link 将图像叠加到相机预览 SurfaceView 上 https stackoverflow com questions 3548666 overlay
  • HTML5 视频:ffmpeg 编码的 MP4 无法在任何浏览器中播放(但可以在 VLC 中播放)

    我正在尝试以 MP4 和 WEBM 格式提供 HTML5 视频 但我无法让所有浏览器都工作 支持 WEBM 的浏览器 Chrome 桌面版 Firefox 桌面版 可以正常播放视频 使用 MP4 的浏览器无法运行 IE Safari And
  • Galaxy Tab 出现奇怪的性能问题

    我正在编写 2d 教程 并且能够在 Samsung Galaxy Tab 上测试我当前的教程部分 本教程只是在屏幕上随机移动默认图标 通过点击 我创建了一个新的移动图标 只要屏幕上有 25 个或更少的元素 Galaxy 上的一切都可以正常运
  • 如何使用 Android 应用程序中的 Intent 播放视频 mp4?这个有可能?

    我需要在我的应用程序中播放 mp4 视频 但我想使用意图 这可能吗 private void startTrailer Uri contentUri Uri parse android resource pkgName R raw v01
  • Android SurfaceView 预览模糊

    我有一个快速的问题 我正在使用 Android 的 SurfaceView 拍照并保存 然而 预览尺寸和图像质量本身都很糟糕 就像 它非常模糊 图像质量根本没有清晰度 这是我初始化 SurfaceView 的地方 camera setDis
  • FFMPEG 在视频末尾添加图像

    我需要使用 FFMPEG 在 mp4 视频文件末尾添加一秒钟的图像 我的视频尺寸是 WxH 图像尺寸是 MxM 因此视频和图像尺寸不同 我尝试了不同的选项 以便在视频末尾添加图像 ffmpeg i concat videoIn mp4 im
  • 使用 imageio 和 Python 当帧率太低时视频是黑色的

    我有以下示例代码 import numpy as np writer imageio get writer test mp4 fps 1 max 800 resolution 256 for idx in range 1 max img n
  • 以编程方式截取屏幕截图不会捕获 surfaceVIew 的内容

    我有一个应用程序 我希望能够捕获屏幕截图 布局的背景是一个 SurfaceView 显示来自后置摄像头的视频 下面的代码可以截图 但是surfaceView的内容保存为黑色 这是代码 btn setOnClickListener new O
  • 色带仅适用于 Android 4.0+

    在运行 Android 4 0 或 4 0 3 的模拟器上 我看到可怕的色带 而且似乎无法摆脱 在我测试过的所有其他 Android 版本上 渐变看起来都很平滑 我有一个配置为 RGBX 8888 的 SurfaceView 并且渲染的画布
  • Android AdMob:addView 在返回活动之前不会显示广告

    我正在尝试在游戏顶部添加横幅广告 我的活动使用带有自定义 SurfaceView 的relativelayout 我希望广告与 SurfaceView 重叠 广告会加载并可点击 但不会绘制到屏幕上 当我离开活动并返回时 会绘制广告 例如 通
  • 在没有 SurfaceView 的 Android 上获取 GPU 信息

    在Android上 有没有一种方法可以在不创建SurfaceView的情况下获取GPU信息 我不想使用 OpenGL 绘制任何内容 但我只需要获取硬件信息 例如供应商 OpenGL ES 版本 可用扩展等 抱歉 我不知道如何在 Androi
  • Android:在surfaceview上实现admob

    我有一个使用 SurfaceView 的小游戏形式的活动 下面是代码片段 我很困惑如何在 SurfaceView 上实现 admob 请建议 public class DroidzActivity extends Activity priv
  • 如何将H264封装到mp4容器中?

    我有一个程序生成一堆原始 H264 帧 并希望将其放入 mp4 容器中进行流式传输 有人知道该怎么做吗 我想我会使用 ffmpeg 然而 这需要商业使用 而且 ffmpeg 似乎只能通过它的 x264 库来做到这一点 它使用 GPL 许可证
  • HTML5 视频(webm 和 MP4)显示空白屏幕并且无法播放

    我已经用谷歌搜索了一个小时 但我要么很糟糕 要么这不是一个常见问题 这是我的视频标签
  • 使用 gtk+ 播放视频

    您对使用 GTK 播放视频有什么建议吗 对于在 GTK 和其他 GTK 绑定上播放视频 您有很多选择 选项 使用第三方库 1 尝试使用ogmrip gtk http ogmrip sourceforge net en manual html
  • 使用 php 将 HLS Segment (ts) 视频转换并加入到 mp4

    你好我正在使用这个工具 https github com Ejz HLSDownloader https github com Ejz HLSDownloader将 HLS 视频片段从 m3u8 播放列表下载到 ts 文件中 不 我不知道如

随机推荐

  • 保存并查看Lego-Loam的三维点云地图

    Loam的安装及运行方法可以参考 https blog csdn net qq 36396941 article details 82973772 本文提供ROS wiki http wiki ros org loam velodyne上无
  • Could not find a package configuration file provided by “OpenCV“ with any of the following names

    CMake Error at opt ros kinetic share catkin cmake catkinConfig cmake 83 find package Could not find a package configurat
  • EKF的通俗理解

    导 xff1a ekf xff0c 扩展卡尔曼滤波简称 xff0c 应用非常广泛 xff1b 1 五个黄金公式 2 应用场合 1 xff09 找清楚模型 2 xff09 对准五个公式的公式 3 xff09 实现 xff1a 求革新值 xff
  • error: Could NOT find pugixml (missing: PUGIXML_LIBRARIES PUGIXML_INCLUDE_DIRS)

    解决办法 xff1a sudo apt get install libpugixml dev sudo apt get install libpugixml1v5
  • STM32小四轴超低成本方案开源项目

    先分享几个小四轴无人机项目 新唐M452飞控开源项目 xff0c 虽然完全开源但是还不够成熟 xff0c PID调节感觉还有些问题 助你轻松DIY四轴飞行器 新唐M452飞控套件评测 电路城 MWC 飞控 xff0c 采用arduino编程
  • Autoware 主要模块

    引言 本文参考Autoware wiki overview xff0c 主要描述了Autoware的整体框架和模块描述 xff0c 主要包括感知和规划两大部分 感知包括定位模块 xff0c 检测模块 xff0c 预测模块 定位模块使用3D
  • DWA算法原理

    DWA算法 局部路径规划简介 机器人在获得目的地信息后 xff0c 首先经过全局路径规划规划出一条大致可行的路线 xff0c 然后调用局部路径规划器根据这条路线及costmap的信息规划出机器人在局部时做出具体行动策略 xff0c ROS中
  • ROS与深度相机入门教程:(1)Ubuntu16.04 在ROS中驱动Intel D435i深度相机

    Intel在Github上开源了支持所有RealSense系列相机的SDK 包括了D400 SR300系列深度相机和T265双目跟踪相机 支持Linux Windows Mac OS以及Android 链接 https github com
  • CMakeLists生成和载入动态链接库

    CMakeLists生成和载入动态链接库 生成动态链接库 新建一个文件夹 xff0c 暂且命名为 makeDllLib 文件夹中放入三个文件 c和 h和 def 其中 def文件是非必须的 xff0c 但它有利于生成 lib文件和导出函数
  • 串口传输 波特率 延时时间的设置

    在进行串口传输的时候 xff0c 波特率太低接收不到 xff0c 波特率太高又丢包 首先 xff0c 传输的报文需要多少时间 起始位1 xff0c 停止位1 xff0c 数据位8 xff0c 则传输时间为 xff1a 1000 xff08
  • 阶段进度条

    public class PhaseProgressView extends View 节点连线宽度 private int mLineWidth 节点个数 private int mNodeNum 选中节点位置 private int m
  • 计算机网络实验——基于TCP协议的socket编程

    一 实验目的 1 实现一个能够在局域网中进行点对点聊天的实用程序 2 熟悉c 43 43 Java等高级编程语言网络编程的基本操作 3 基本了解对话框应用程序的编写过程 4 实现TCP套接字编程 二 实验内容 xff08 一 xff09 实
  • GPRMC 格式

    数据示例 GPRMC 081057 000 A 3117 2144 N 12133 2691 E 0 02 0 00 140521 D 68 在数据 GPRMC lt 1 gt lt 2 gt lt 3 gt lt 4 gt lt 5 gt
  • 音视频开发基础概念

    对一个初学者来说 xff0c 刚刚接触音视频的学习难免会遇到各种个样的术语 xff0c 一开始我也是云里雾里的 xff0c 到现在一点一点接触积累 xff0c 形成一个基本的认识 本文并没有什么高深和详细的知识点 xff0c 旨在记录一些音
  • 音频数据采集-AudioRecord

    一 AudioRecord 和 MediaRecorder Android 提供了两个 API 用于录音 xff0c AudioRecord 和 MediaRecorder AudioRecord 能够获取原始的 PCM 数据 xff0c
  • vector用法

    介绍 1 vector是表示可变大小数组的序列容器 2 就像数组一样 xff0c vector也采用的连续存储空间来存储元素 也就是意味着可以采用下标对vector的元素进行访问 xff0c 和数组一样高效 但是又不像数组 xff0c 它的
  • AAC 音频编码保存和解码播放

    一 编码器 MediaCodec MediaCodec 是 Android 提供的用于对音频进行编解码的类 xff0c 属于硬编解 MediaCodec 在编解码的过程中使用了一组缓冲区来处理数据 如下图所示 基本使用流程如下 xff1a
  • Camera 视频采集,H264 编码保存

    一 前言 上篇文章 AAC 音频编码保存和解码播放 讲述了通过 AudioRecord 录制音频数据 xff0c 并通过 AAC 编码保存为 AAC 文件 这里的 aac 既是一种编码方式 xff0c 也是一种容器 xff0c 因此可以直接
  • 基于Camera、AudioRecord 、MediaCodec 和 MediaMuxer 录制 MP4

    一 前言 在 AAC 音频编码保存和解码播放和Camera 视频采集 xff0c H264 编码保存 两篇文章中介绍了如何通过 AudioRecord 和 MediaCodec 录制 AAC 音频以及如何通过 Camera 和 MediaC
  • 基于 SurfaceView、AudioTrack、MediaCodec 和 MediaExtractor 解码 MP4 播放

    一 前言 上篇文章介绍了 基于Camera AudioRecord MediaCodec 和 MediaMuxer 录制 MP4 录制的过程是这样的 xff0c 那么相应的播放过程就是上述过程的逆过程 xff0c 本篇文章将介绍如何通过 M