MediaExtractor与MediaCodec使用方法

    在学习了AudioTrack播放pcm数据之后,又了解到很多APP不是使用MediaPlayer对音视频文件进行播放的。而是使用解码器,从音视频文件中解码出pcm原生数据,然后使用AudioTrack.java将音频播放出来。所有对其实现过程产生了兴趣,并进行了学习。总结如下:

1. MediaExtractor和MediaCodec的初认知:

   MediaExtractor:a. 将音视频文件解析出音轨和视轨数据; b.可以获取音/视轨的参数信息(如getTrackFormat()获得mediaFormat后,从mediaFormat中可得到视频的width/height/duration等数据)

    MediaCodec:将音视频文件解码成可以用Surface显示和用AudioTrack播放类型的数据。

2.MediaExtractor/MediaCodec基本使用方法:

    A. 视频数据的显示

MediaExtractor videoExtractor = new MediaExtractor();     //创建对象
videoExtractor.setDataSource(sourcePath);        //设置需播放的视频文件路径
-------------------------------------------------------------------------------
//接下来需要做的是,从videoExtractor中找到视轨的id,方法如下:
int videoTrackIndex = -1;                                //定义trackIndex为视轨的id
for (int i = 0; i < videoExtractor.getTrackCount(); i++) {       //在videoExtractor的所以Track中遍历,找到视轨的id
    MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);    //获得第id个Track对应的MediaForamt
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME);    //再获取该Track对应的KEY_MIME字段
    if (mime.startsWith("video/")) {                 //视轨的KEY_MIME是以"video/"开头的,音轨是"audio/"
        trackIndex = i;
        break;
    }
}
-------------------------------------------------------------------------------
videoExtractor.selectTrack(videoTrackIndex);  //选择视轨所在的轨道子集(这样在之后调用readSampleData()/getSampleTrackIndex()方法时候,返回的就只是视轨的数据了,其他轨的数据不会被返回)
MediaFormat mediaFormat = videoExtractor.getTrackFormat(videoTrackIndex); //根据视轨id获得对应的MediaForamt
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
int time = mediaFormat.getInteger(MediaFormat.KEY_DURATION);  
//从得到的MediaFormat中,可以获取视频的相关信息,视频的长/宽/时长等
--------------------------------------------------------------------------------
    在通过MediaExtractor获得需要解码的音轨的id后,就可以创建对应的MediaCodec来解析数据了
mVideoCodec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); //参数为MediaFormat类中的MIMETYPE
mVideoCodec.configure(mediaFormat, surface, null, 0); //第一个参数是待解码的数据格式(也可用于编码操作);第二个参数是设置surface,用来在其上绘制解码器解码出的数据;第三个参数于数据加密有关;第四个参数上1表示编码器,0是否表示解码器呢??
mVideoCodec.start();  //当configure好后,就可以调用start()方法来请求向MediaCodec的inputBuffer中写入数据了
-------------------------------------------------------------------------------    
知识点:MediaCodec类中有三个方法与数据读写有关:queueInputBuffer()/dequeueInputBuffer()/dequeueOutputBuffer() 
MediaCodec中有维护这两个缓冲区,分别存放的是向MediaCodec中写入的数据,和经MediaCodec解码后写出的数据
dequeueInputBuffer(): Returns the index of an input buffer to be filled with valid data
dequeueOutputBuffer():Returns the index of an output buffer that has been successfully decoded
queueInputBuffer(): After filling a range of the input buffer at the specified index submit it to the component  
  
    接下来要做到就是,向MediaCodec的inputBuffer中写入数据,而数据就是来自上面MediaExtractor中解析出的Track,代码如下:
ByteBuffer[] inputBuffers = mVideoCodec.getInputBuffers();   //获取MediaCodec中等待数据写入的ByteBuffer的集合,大概有10个ByteBuffer
    上面这个方法获取的是整个待写入数据的ByteBuffer的集合,在MediaExtractor向MediaCodec中写入数据的过程中,需要判断哪些ByteBuffer
是可用的,这就可以通过dequeueInputBuffer得到。
int inputBufferIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_US); //得到那个可以使用的ByteBuffer的id
if (inputBufferIndex >= 0) {   //返回的inputBufferIndex为-1,说明暂无可用的ByteBuffer
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];   //有可以就从inputBuffers中拿出那个可用的ByteBuffer的对象
int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);  //把MediaExtractor中的数据写入到这个可用的ByteBuffer对象中去,返回值为-1表示MediaExtractor中数据已全部读完
if (sampleSize < 0) {
      isMediaEOS = true;         
      mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
   } else {
      mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0); //将已写入数据的id为inputBufferIndex的ByteBuffer提交给MediaCodec进行解码
      mediaExtractor.advance();  //在MediaExtractor执行完一次readSampleData方法后,需要调用advance()去跳到下一个sample,然后再次读取数据
   }
}

最后一步的作用容易被忽视掉,但是如果没有的话,也会导致视频无法播放出来,代码如下:
int outputBufferIndex = mVideoCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_US);  //获得已经成功解码的ByteBuffer的id
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:
        Log.v(TAG, "output buffers changed");
        break;
    default: 
       mVideoCodec.releaseOutputBuffer(outputBufferIndex, true);   
       break; 
//将该ByteBuffer释放掉,以供缓冲区的循环使用。如果没有这一步的话,会导致上面返回的inputBufferIndex一直为-1,使数据读写操作无法进行下去。如果在configure中配置了surface,则首先将缓冲区中数据发送给surface,surface一旦不再使用,就将缓冲区释放给MediaCodec 
注意:本段的数据读写操作,应该是循环的。它的中断/结束条件是:a.停止播放视频    b.视频播放结束(结束的标志可从sampleSize是否为-1来判断) 

    B. 音频数据的播放

 音频数据的播放和视频数据的显示,大体流程相同,不过有几点需要说明下:

MediaExtractor audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(sourcePath);
for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
  MediaFormat mediaFormat = audioExtractor.getTrackFormat(i);
   String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
   if (mime.startsWith("audio/")) {
    audioExtractor.selectTrack(i);
    int audioChannels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    int audioSampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    int minBufferSize = AudioTrack.getMinBufferSize(audioSampleRate,(audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO), AudioFormat.ENCODING_PCM_16BIT);     
    int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
        audioInputBufferSize = minBufferSize > 0 ? minBufferSize * 4 : maxInputSize;
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,audioSampleRate,(audioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO), AudioFormat.ENCODING_PCM_16BIT,audioInputBufferSize,AudioTrack.MODE_STREAM);
      audioTrack.play();
      audioCodec = MediaCodec.createDecoderByType(mime);
      audioCodec.configure(mediaFormat, null, null, 0);     //因为是音轨,所以第二参数为null 
      audioCodec.start();
     ByteBuffer[] outputBuffers = audioCodec.getOutputBuffers();
     ByteBuffer[] inputBuffers = audioCodec.getInputBuffers();
     MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();

然后就是将MediaExtractor的数据写入到MediaCodec中,然后利用dequeueOutputBuffer()将已经成功解析出pcm数据的ByteBuffer的id得到,获得pcm保存的数组outputBuffer。

int outputBufferIndex = audioCodec.dequeueOutputBuffer(audioBufferInfo, 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 = audioCodec.getOutputBuffers();      //注意:当dequeueOutputBuffer返回的id为-3时,说明缓存区发生了改变,必须重新通过getOutputBuffers来获得新的outputBuffer对象,不能再使用之前创建的了。
        Log.v(TAG, "output buffers changed");
        break;
    default:
        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; //1. 视频可以直接显示在Surface上,音频需要获取pcm所在的ByteBuffer           
     byte[] tempBuffer = new byte[outputBuffer.limit()]           
     outputBuffer.position(0);
        outputBuffer.get(tempBuffer, 0, outputBuffer.limit());      //2.将保存在ByteBuffer的数据,转到临时的tempBuffer字节数组中去
        outputBuffer.clear();
        if (audioTrack != null)       
    audioTrack.write(tempBuffer, 0, audioBufferInfo.size);      //3.最后写到AudioTrack中
        audioCodec.releaseOutputBuffer(outputBufferIndex, false);   //4. 释放该id的ByteBuffer
        break;
}











  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
可以使用以下步骤从mp4视频文件中提取帧缩略图: 1. 使用MediaExtractor从mp4文件中提取视频轨道数据。 ```java MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(filePath); int trackCount = extractor.getTrackCount(); for (int i = 0; i < trackCount; i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("video/")) { extractor.selectTrack(i); ByteBuffer inputBuffer = ByteBuffer.allocate(1024 * 1024); MediaCodec codec = MediaCodec.createDecoderByType(mime); codec.configure(format, null, null, 0); codec.start(); boolean isEOS = false; while (!isEOS) { int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { isEOS = true; } else { long presentationTimeUs = extractor.getSampleTime(); codec.queueInputBuffer(inputBuffer.position(), sampleSize, presentationTimeUs, 0, isEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); extractor.advance(); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); while (outputBufferIndex >= 0) { Image image = codec.getOutputImage(outputBufferIndex); // 处理image,生成缩略图 codec.releaseOutputBuffer(outputBufferIndex, true); outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); } } codec.stop(); codec.release(); extractor.unselectTrack(i); } } extractor.release(); ``` 2. 使用MediaCodec进行视频解码,获取每一帧的Image对象。 3. 将Image对象转换为Bitmap对象,生成缩略图。 ```java Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * width; Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); ``` 4. 可以使用OpenGL对缩略图进行后期处理,如旋转、缩放等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值