4.使用MediaExtractor+MediaCodec+AudioTrack播放音频文件

使用MediaExtractor+MediaCodec+AudioTrack播放音频文件

首先来看一张图:

要想播放一个视频,就得对视频文件解协议、解封装,随后可以得到音频数据和视频数据(分开的),再次分别解码,最后同步播放(因为播放音频和视频的设备不同,所以需要将音视频同步)。

在这里插入图片描述

本任务就是实现该图的左半边,进行音频的播放。
(前几个任务中的MediaPlayer是帮我们完成了解封、解码以及同步的操作)

MediaExtractor

用来把视频和音频的数据进行分离

对于一个常见的视频文件,通常有一个视频轨道,和一个音频轨道,通过MediaExtractor来分离音视频轨道数据

当然可能一个视频文件中有多个视频流和多个音频流,

解封装的步骤体现在MediaExtractor中,MediaExtractor从封装格式中分离获取不同format的数据(video/audio)

MediaCodec

解码的步骤体现在MediaCodec中,MediaCodec对特定格式的数据进行解码获取音频原始数据。

MediaCodec有两个ByteBuffer数组,分别是InputBuffers,OutputBuffers用来导入导出解码前后的数据。大致过程是:

  1. 向MediaCodec中传入待解码的数据
  2. 解码(自动进行)
  3. 获取解码后的数据
  4. 渲染到Surface或载入到AudioTrack

States

Stopped, Executing or Released
在这里插入图片描述

首先利用 configure(…)来配置,然后调用start()进入Executing状态,编解码器立即处于刷新子状态,它在其中保存所有缓冲区。

利用stop()来进入Uninitialized状态,如果还需要使用就得再配置。

当时用完后codec后,release()来结束

Create

createDecoderByType(String)

createByCodecName(String)

AudioTrack&Surface

解码后,结合Surface和AudioTrack进行音视频播放。

AudioTrack播放音频是需要获取到音频相关的声道数、采样率,以此来配置。

MediaPlayer可以播放多种格式的声音文件,例如MP3,WAV,OGG,AAC,MIDI等。然而AudioTrack 只能播放PCM数据流。当然两者之间还是有紧密的联系,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,最后由AudioFlinger进行混音,传递音频给硬件播放出来。利用AudioTrack播放只是跳过Mediaplayer的解码部分而已。参考链接

AudioTrack实现PCM音频播放五步走

  • 配置基本参数

  • 获取最小缓冲区大小

    bufferSizeInBytes参数(构建AudioTrack时的一个入参):

    If the track’s creation mode is MODE_STREAM , this should be the desired buffer size for the AudioTrack to satisfy the application’s latency requirements. If bufferSizeInBytes is less than the minimum buffer size for the output sink, it is increased to the minimum buffer size. The method getBufferSizeInFrames() returns the actual size in frames of the buffer created, which determines the minimum frequency to write to the streaming AudioTrack to avoid underrun. See getMinBufferSize(int,int,int) to determine the estimated minimum buffer size for an AudioTrack instance in streaming mode. --------来自谷歌
    可以利用 getBufferSizeInFrames()getMinBufferSize(int,int,int)来获取最小缓冲区大小。

  • 创建AudioTrack对象

    AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)
    此外建议使用Builder来构建AudioTrack---------AudioTrack.Builder | Android Developers (google.cn)

    Class constructor with AudioAttributes and AudioFormat.

    AudioAttributes 封装音频流信息的类
    A class to encapsulate a collection of attributes describing information about an audio stream.

    • usage: “why” you are playing a sound, what is this sound used for. This is achieved with the “usage” information. Examples of usage are USAGE_MEDIA and USAGE_ALARM.
    • content type: “what” you are playing. The content type expresses the general category of the content. This information is optional. CONTENT_TYPE_MOVIE or CONTENT_TYPE_MUSIC.
    • flags: “how” is playback to be affected, see the flag definitions for the specific playback behaviors they control.

    AudioFormat 音频帧 此类用于访问音频 格式和通道配置常量
    The AudioFormat class is used to access a number of audio format and channel configuration constants.

    • Sample rate:播放内容的采样率
    • Encoding:音频编码方式,对于线性 PCM(因为AudioTrack仅支持PCM编码的数据),音频编码描述样本大小(8 位、16 位或 32 位)以及样本表示形式(整数或浮点数)。
      When compressed audio is sent out through a direct AudioTrack, it need not be written in exact multiples of the audio access unit; this differs from MediaCodec input buffers.
    • Channel masks:声道掩码,用于描述样本及其在音频帧中的排列。
      有两种类型的声道掩码:
      • Channel position masks:original。不同的声道有着不同的位置安排 ,CHANNEL_OUTLEFT
      • Channel index masks:通过index来 选定特定的声道,类似于子网掩码的感觉
  • 获取PCM文件,转成DataInputStream

  • 开启/停止播放

    If you don’t call write() first, or if you call write() but with an insufficient amount of data, then the track will be in underrun state at play().

    只有在利用write()向缓冲区写入数据时,才会播放,否则play()后只会进入一个就绪状态。

Code

Android MediaCodec解码音频,AudioTrack播放音频,PCM数据写入文件-CSDN博客

使用MediaExtractor , MediaCodec对音视频的解码实现简单播放器_mediacode 解码音频播放-CSDN博客

首先需要进行相关的初始化工作,然后在进行解码的操作,解码会把相关数据加载到缓存区然后写入AudioTrack中进行播放,然后再清空该缓存区,随后一直循环到文件结束。

初始化MediaExtractor、AudioTrack、MediaCodec

@RequiresApi(api = Build.VERSION_CODES.M)
private void initPlayer() throws IOException {
    // 选取音频轨道
    extractor = new MediaExtractor();
    try {
        //extractor.setDataSource("//sdcard/Movies/big_buck_bunny.mp4");
        extractor.setDataSource("//sdcard/Movies/lesson.mp4");
    } catch (IOException e) {
        e.printStackTrace();
    }
    int numTracks = extractor.getTrackCount();
    int trackIndex = 0;
    for (int i = 0; i < numTracks; ++i) {
        MediaFormat format = extractor.getTrackFormat(i);
        String mime = format.getString(MediaFormat.KEY_MIME);
        // 可获取该format所对应的数据类型(以键值对实现,KEY_MIME这个key所对应value记录的是该数据流类型,
        // 视频以video/开头,音频以audio/开头)
        if (mime.startsWith("audio/")) {
            trackIndex = i;
         }
    }
    MediaFormat audioFormat = extractor.getTrackFormat(trackIndex);
    Log.i(TAG, String.valueOf(audioFormat));
    extractor.selectTrack(trackIndex);

    // 获取声道数、采样率 用于计算最小的缓存区大小
    int audioChannels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    int audioSampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    Log.i(TAG, "Channels is " + audioChannels + " SamplerRate is " + audioSampleRate);
    int minBuffSize = AudioTrack.getMinBufferSize(audioSampleRate,
    AudioFormat.CHANNEL_OUT_MONO,
    AudioFormat.ENCODING_PCM_16BIT);
    Log.i(TAG, "minBuffSize is " + minBuffSize);
    // 配置配置AudioTrack
    audioTrack = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build())
        .setAudioFormat(new AudioFormat.Builder()
             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
             .setSampleRate(audioSampleRate)
             .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
             .build())
        .setBufferSizeInBytes(Math.max(minBuffSize, 2048))
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build();
    // AudioTrack这里的play()是一个就绪状态,它需要从缓冲区中读取数据才能播放,所以只有在MediaCodec将数据解码输入到缓冲区后才可以播放
    audioTrack.play();


    // MediaCodec 解码器的配置
    audioCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
    Log.i(TAG, "MediaCodec.createDecoderByType‘s audioFormat is " + audioFormat.getString(MediaFormat.KEY_MIME));
    audioCodec.configure(audioFormat, null, null, 0);
    audioCodec.start();
}

播放音频

private void decodeAudio() {
    MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
    long startMs = System.currentTimeMillis();
    ByteBuffer inputBuffer;
    boolean isEOS = false;
    while (!isEOS && !Thread.currentThread().isInterrupted()) {
	// !Thread.currentThread().isInterrupted() 是用来获取当前线程是否被中断,如果被中断就直接结束了
        // 获取可用的输入缓冲区的索引
        int inputBufferId = audioCodec.dequeueInputBuffer(10000);
        Log.i(TAG, "The inputBufferId is " + inputBufferId);
        if (inputBufferId >= 0) {
            // 获取输入缓冲区,并向缓冲区写入数据
            inputBuffer = audioCodec.getInputBuffer(inputBufferId);
            int sampleSize = extractor.readSampleData(inputBuffer, 0);
            if (sampleSize > 0) {
                // 将填满数据的inputBuffer提交到编码队列
                audioCodec.queueInputBuffer(inputBufferId, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            } else {
                isEOS = true;
                break;
            }
        }
        // 获取已成功编解码的输出缓冲区的索引
        int outputBufferId = audioCodec.dequeueOutputBuffer(audioBufferInfo, 10000);
        Log.i(TAG, "the outputBufferId is " + outputBufferId);
        byte[] pcmData;
        while (outputBufferId >= 0) {
            // 获取输出缓冲区
            ByteBuffer outputBuffer = audioCodec.getOutputBuffer(outputBufferId);
            pcmData = new byte[audioBufferInfo.size];
            if (outputBuffer != null) {
                outputBuffer.get(pcmData);
                outputBuffer.clear();//用完后清空,复用
            }
            audioTrack.write(pcmData, 0, audioBufferInfo.size);
            Log.i(TAG, "audioTrack.write");
            //取解码后的数据
            int outputIndex = audioCodec.dequeueOutputBuffer(audioBufferInfo, 10000);

            audioCodec.releaseOutputBuffer(outputBufferId, false);
            outputBufferId = audioCodec.dequeueOutputBuffer(audioBufferInfo, 10000);
        }
    }
    audioCodec.stop();
    audioCodec.release();
    extractor.release();
    audioTrack.stop();
    audioTrack.release();
}

中断线程

当线程的run方法执行方法体中最后一条语句执行后再执行return语句返回时,或者出现了方法中没有捕获的异常,线程将会被终止。

Java早期版本中,还有一个stop方法来终止线程,不过此方法已经被弃用。

现在除了已经废弃的stop方法(因为不安全),没有办法可以强制线程终止。

不过可以用 interrupt方法来请求终止一个线程:

当对一个线程调用interrupt方法时,就会设置线程的中断状态,这是一个boolean值,每个线程都会时不时地检查这个标志,以判断线程是否被中断。

while (!Thread.currentThread().isInterrupted()) 
{
	do more work
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用以下步骤从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对缩略图进行后期处理,如旋转、缩放等操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值