Android提供了MediaPlayer播放器播放媒体文件,其实MediaPlyer只是对Android Media包下的MediaCodec和MediaExtractor进行了包装,方便使用。但是最好理解下Android媒体文件的解码,编码和渲染流程。

Shape Of My Heart.mp4

使用android.media包下的MediaCodec和MediaExtractor实现一个简单的视频解码渲染。

使用到了:

  • MediaCodec:负责媒体文件的编码和解码工作,内部方法均为native
  • MediaExtractor:负责将指定类型的媒体文件从文件中找到轨道,并填充到MediaCodec的缓冲区中
  • AudioTrack:负责将解码之后的音频播放
  • SurfaceView:展示解码之后的视频

视频被播放主要分为以下步骤:

  1. 将资源加载到extractor
  2. 获取视频所在轨道
  3. 设置extractor选中视频所在轨道
  4. 创将解码视频的MediaCodec,decoder
  5. 开始循环,直到视频资源的末尾
  6. 将extractor中资源以一个单位填充进decoder的输入缓冲区
  7. decoder将解码之后的视频填充到输出缓冲区
  8. decoder释放输出缓冲区的同时,将缓冲区中数据渲染到surface

音频的播放类似,只多了AudioTrack部分,少了渲染到surface部分。

MediaCodec.releaseOutputBuffer(int outputBufferIndex, boolean render);

  • render为true就会渲染到surface

播放的控制,视频和音频各自拥有一个Thread。

    public void play() {
isPlaying = true;
if (videoThread == null) {
videoThread = new VideoThread();
videoThread.start();
}
if (audioThread == null) {
audioThread = new AudioThread();
audioThread.start();
}
} public void stop() {
isPlaying = false;
}

VideoThread

private class VideoThread extends Thread {
@Override
public void run() {
MediaExtractor videoExtractor = new MediaExtractor();
MediaCodec videoCodec = null;
try {
videoExtractor.setDataSource(filePath);
} catch (IOException e) {
e.printStackTrace();
}
int videoTrackIndex;
//获取视频所在轨道
videoTrackIndex = getMediaTrackIndex(videoExtractor, "video/");
if (videoTrackIndex >= 0) {
MediaFormat mediaFormat = videoExtractor.getTrackFormat(videoTrackIndex);
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
//视频长度:秒
float time = mediaFormat.getLong(MediaFormat.KEY_DURATION) / 1000000;
callBack.videoAspect(width, height, time);
videoExtractor.selectTrack(videoTrackIndex);
try {
videoCodec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
videoCodec.configure(mediaFormat, surface, null, 0);
} catch (IOException e) {
e.printStackTrace();
}
} if (videoCodec == null) {
Log.v(TAG, "MediaCodec null");
return;
}
videoCodec.start(); MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
ByteBuffer[] inputBuffers = videoCodec.getInputBuffers();
// ByteBuffer[] outputBuffers = videoCodec.getOutputBuffers();
boolean isVideoEOS = false; long startMs = System.currentTimeMillis();
while (!Thread.interrupted()) {
if (!isPlaying) {
continue;
}
//将资源传递到解码器
if (!isVideoEOS) {
isVideoEOS = putBufferToCoder(videoExtractor, videoCodec, inputBuffers);
}
int outputBufferIndex = videoCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_US);
switch (outputBufferIndex) {
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.v(TAG, "format changed");
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.v(TAG, "解码当前帧超时");
break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
//outputBuffers = videoCodec.getOutputBuffers();
Log.v(TAG, "output buffers changed");
break;
default:
//直接渲染到Surface时使用不到outputBuffer
//ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
//延时操作
//如果缓冲区里的可展示时间>当前视频播放的进度,就休眠一下
sleepRender(videoBufferInfo, startMs);
//渲染
videoCodec.releaseOutputBuffer(outputBufferIndex, true);
break;
} if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.v(TAG, "buffer stream end");
break;
}
}//end while
videoCodec.stop();
videoCodec.release();
videoExtractor.release();
}
}

获取指定类型媒体文件所在轨道

    //获取指定类型媒体文件所在轨道
private int getMediaTrackIndex(MediaExtractor videoExtractor, String MEDIA_TYPE) {
int trackIndex = -1;
for (int i = 0; i < videoExtractor.getTrackCount(); i++) {
//获取视频所在轨道
MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);
String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith(MEDIA_TYPE)) {
trackIndex = i;
break;
}
}
return trackIndex;
}

将缓冲区传递至解码器

    //将缓冲区传递至解码器
private boolean putBufferToCoder(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] inputBuffers) {
boolean isMediaEOS = false;
int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isMediaEOS = true;
Log.v(TAG, "media eos");
} else {
decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
return isMediaEOS;
}

音频的部分类似,完整源码请移步jiyangg/MediaPlaySimpleDemo

Android媒体解码MediaCodec,MediaExtractor的更多相关文章

  1. Android媒体解码MediaCodec MediaExtractor学习

    Android提供了MediaPlayer播放器播放媒体文件,其实MediaPlyer只是对Android Media包下的MediaCodec和MediaExtractor进行了包装,方便使用.但是 ...

  2. Android MediaPlayer 和 MediaCodec 的区别和联系(一)

    目录: (1)概念解释 : 硬解.软解 (2)Intel关于Android MediaCodec的相关说明 正文: 一.硬解.软解         (1)概念:                 a.硬 ...

  3. Android 媒体编解码器(转)

    媒体编解码器 MediaCodec类是用来为低级别的媒体编码和解码的媒体编解码器提供访问.您可以实例化一个MediaCodec类通过调用createEncoderByType()方法来进行对媒体文件进 ...

  4. Android 媒体存储服务(二)

    Android 媒体存储服务 简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套 ...

  5. Android 媒体存储服务(一)

    Android 媒体存储服务 本文介绍如何在 Android 中,开发者的 APP 如何使用媒体存储服务(包含MediaScanner.MediaProvider以及媒体信息解析等部分),包括如何把 ...

  6. 专题合集:深入Android媒体存储服务

    Android 有一套媒体存储服务,进程名是 android.process.media,主要负责把磁盘中的文件信息保存到数据库当中,供其他 APP 使用以及 MTP 模式使用.这里包含了数据库管理. ...

  7. 深入Android媒体存储服务(二):磁盘扫描流程

    简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套媒体存储服务,进程名是 and ...

  8. android媒体--图库与API层MediaPlayer的交互

    众所周知一个媒体播放器新建的几个步骤: Mediaplayer mp = new MediaPlayer(0 mp.setDatasource(xxx); mp.setDispalyer(xxx); ...

  9. Android——媒体库 相关知识总结贴

    Android媒体库 http://www.apkbus.com/android-19283-1-1.html Android本地图片选择打开媒体库,选择图片 http://www.apkbus.co ...

随机推荐

  1. BZOJ [POI2004]PRZ 状压DP_二进制_骚操作

    二进制就是比谁更sao... Code: #include <bits/stdc++.h> #define setIO(s) freopen(s".in"," ...

  2. esp32(M5STACK)程序烧写(Ubuntu)

    由于我们的开发环境在Ubuntu上,所以介绍一下如何在Ubuntu上烧写esp32的程序 首先下载esptools   pip install esptool           擦除 sudo es ...

  3. windows开机自启动bat脚本设置

    群里有朋友问到windows下如何设计开机自启动的脚本,一般而言小鱼大多还是在linux环境下运维,windows的bat了解的很少,windows运行机制也不是特别了解,不过既然朋友问到这个问题,虽 ...

  4. [luogu1397 NOI2013] 矩阵游戏 (数学)

    传送门 Description 婷婷是个喜欢矩阵的小朋友,有一天她想用电脑生成一个巨大的n行m列的矩阵(你不用担心她如何存储).她生成的这个矩阵满足一个神奇的性质:若用F[i][j]来表示矩阵中第i行 ...

  5. Linux思维导图之rpm、yum、编译

    yum安装失败: 1.yum client 路径指向不正确:2.yum server 缓存未清理(yum clean all;yum makecache):3.网络不连通

  6. salt 批量添加route路由

    安装net-tools包 因为其余机器没有网络,使用rpm包安装,并添加缺省路由. [root@web1 base]# tree . ├── add-route.sls ├── files │   └ ...

  7. Tp5 一次修改多个数据update

    //商城矿机设置 public function shop(){ if(!request()->isPost()){ return $this->fetch(); }else { $myd ...

  8. TotoiseSVN使用教程

    TortoiseSVN百科 TortoiseSVN 是 Subversion 版本控制系统的一个免费开源客户端,可以超越时间的管理文件和目录.文件保存在中央版本库,除了能记住文件和目录的每次修改以外, ...

  9. 用 Python 理解 Web 并发模型

    用 Python 理解 Web 并发模型 http://www.jianshu.com/users/1b1fde012122/latest_articles   来源:MountainKing 链接: ...

  10. spring boot下接口调用失败重试方案

    背景: 在项目开发中,有时候会出现接口调用失败,本身调用又是异步的,如果是因为一些网络问题请求超时,总想可以重试几次把任务处理掉. 一些RPC框架,比如dubbo都是有重试机制的,但是并不是每一个项目 ...