Android音频学习之MediaExtractor,提取音频视频轨道数据

版权声明:本文为博主原创文章,转载希望能注明出处,感谢。 https://blog.csdn.net/u010126792/article/details/86497571

一个音视频文件是由音频和视频组成的,我们可以通过MediaExtractor、MediaMuxer把音频或视频给单独抽取出来,抽取出来的音频和视频能单独播放;

1 MediaExtractor 说明

MediaExtractor从api16开始添加,可用于分离视频文件的音轨和视频轨道,如果你只想要视频,那么用selectTrack方法选中视频轨道,然后用readSampleData读出数据,这样你就得到了一个没有声音的视频,想得到音频也可以用同样的方法。

MediaExtractor构造函数:
MediaExtractor()

MediaExtractor 主要方法说明:

设置数据源:
setDataSource(AssetFileDescriptor afd)
setDataSource(Context context, Uri uri, Map<String, String> headers)
setDataSource(FileDescriptor fd)
setDataSource(MediaDataSource dataSource)
setDataSource(FileDescriptor fd, long offset, long length)
void setDataSource(String path)//既可以是文件路径,也可以是文件url网址。
void setDataSource(String path, Map<String, String> headers)//既可以是文件路径,也可以是文件url网址。

获取源文件轨道数(包括视频和音频)
getTrackCount()

获取某个轨道的MediaFormat:
MediaFormat getTrackFormat(int index),MediaFormat包含该轨道的很多配置信息。

选定轨道:
selectTrack(int index):选定特定的轨道,会影响 readSampleData(ByteBuffer, int), getSampleTrackIndex() and getSampleTime()的输出,这三个函数输出的是选定轨道的信息。

读取信息:
readSampleData(ByteBuffer byteBuf, int offset):读取数据到bytebuffer中,从offset偏移开始。选定轨道之后可以读取该轨道的数据。

获取下一帧数据:
advance()获取下一帧数据

释放资源:
release()释放资源。

2 MediaFormat

MediaFormat封装了Media data的描述信息,通过描述信息可以分辨Meida data 是一个音频还是视频。描述信息是一个键值对,可以通过MediaFormat的getXX函数获取。

MediaFormat 可以通过MediaExtractor 的getTrackFormat(Index) 获取,也可以通过构造函数自己构造。

MediaFormat构造函数:
MediaFormat()

createAudioFormat(String mime, int sampleRate, int channelCount)
创建一个音频的MediaFormat
参数说明:

  • mime:文件类型
  • sampleRate:采样率
  • channelCount:声音轨道数

Mime文件类型:
常见的MIME类型多媒体格式如下(以audio开头的是音频,以video开头的是视频):

  • “video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
  • “video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
  • “video/avc” - H.264/AVC video
  • “video/mp4v-es” - MPEG4 video
  • “video/3gpp” - H.263 video
  • “audio/3gpp” - AMR narrowband audio
  • “audio/amr-wb” - AMR wideband audio
  • “audio/mpeg” - MPEG1/2 audio layer III
  • “audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
  • “audio/vorbis” - vorbis audio
  • “audio/g711-alaw” - G.711 alaw audio
  • “audio/g711-mlaw” - G.711 ulaw audio
  • 。。。还有很多格式请参考MediaFormat中的格式常量
    字幕Track格式:
    MIMETYPE_TEXT_VTT = “text/vtt”;
    MIMETYPE_TEXT_CEA_608 = “text/cea-608”;
    MIMETYPE_TEXT_CEA_708 = “text/cea-708”;

MediaFormat中mime有对应的常量,MIMETYPE_AUDIO_AAC对应audio/mp4a-latm,MIMETYPE_VIDEO_AVC对应 "video/avc"等等,其他对应参考MediaFormat内常量。

至于这些类型对应什么类型的文件这里举几个例子:mp3为audio/mpeg;aac为audio/mp4a-latm;mp4为video/mp4v-es 。

createVideoFormat(String mime, int width, int height)
创建一个视频的MediaFormat,mime含义和上面相同,width,height含义为视频宽高。

MediaExtractor的getTrackFormat也可以获取MediaFormat。

通过key值获取信息的getXXX函数:
getFloat(String name)
getInteger(String name)
getLong(String name)
getString(String name)
利用MediaFormat的key常量获取值。

常见的key值包括:
KEY_MIME,KEY_CHANNEL_COUNT,KEY_SAMPLE_RATE,KEY_DURATION,KEY_WIDTH,KEY_HEIGHT等。

MediaExtractor基本用法(来自Developers):

MediaExtractor extractor = new MediaExtractor();
 extractor.setDataSource(...);
 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);
   }
 }
 ByteBuffer inputBuffer = ByteBuffer.allocate(...)
 while (extractor.readSampleData(inputBuffer, ...) >= 0) {
   int trackIndex = extractor.getSampleTrackIndex();
   long presentationTimeUs = extractor.getSampleTime();
   ...
   extractor.advance();
 }
 extractor.release();
 extractor = null;

3 从mp4文件中获取信息并提取音频文件和视频文件

从mp4文件中提取音频和视频轨道的数据,得到aac音频数据和.h264视频数据。
代码示例:为测试代码,只展示使用过程,要使用需要优化

//提取的文件为什么是aac后面会说明
private  String pcmPath = Environment.getExternalStorageDirectory()
        .getPath() + "/demo/test.aac";

//为什么是.h264 后面会说明
private  String mp4Path = Environment.getExternalStorageDirectory()
        .getPath() + "/demo/test.h264";
private  String dirPath = Environment.getExternalStorageDirectory()
        .getPath() + "/demo";

private MediaExtractor mediaExtractor;

private void initMediaDecode() {
    String srcPath = Environment.getExternalStorageDirectory()
            .getPath() + "/video/video.mp4";
    File file = new File(dirPath);
    if (!file.exists()){
        file.mkdir();
    }

    File file1 = new File(pcmPath);
    File file2 = new File(mp4Path);
    try {
        if (file1.exists()){
            file1.delete();

        }
        file1.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        if (file2.exists()){
            file2.delete();
        }
        file2.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
        mediaExtractor.setDataSource(srcPath);//媒体文件的位置
        System.out.println("==========getTrackCount()===================" + mediaExtractor.getTrackCount());
        for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道,包括视频和音频轨道
            MediaFormat format = mediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("audio")) {//获取音频轨道
                mediaExtractor.selectTrack(i);//选择此音频轨道
  System.out.println("====audio=====KEY_MIME========="+format.getString(MediaFormat.KEY_MIME));         System.out.println("====audio=====KEY_CHANNEL_COUNT======="+format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)+"");             System.out.println("====audio=====KEY_SAMPLE_RATE==========="+format.getInteger(MediaFormat.KEY_SAMPLE_RATE)+"");
System.out.println("====audio=====KEY_DURATION==========="+format.getLong(MediaFormat.KEY_DURATION)+"");

System.out.println("====audio=====getSampleFlags==========="+mediaExtractor.getSampleFlags()+"");
System.out.println("====audio=====getSampleTime==========="+mediaExtractor.getSampleTime()+"");
//  System.out.println("====audio=====getSampleSize==========="+mediaExtractor.getSampleSize()+"");api28
 System.out.println("====audio=====getSampleTrackIndex==========="+ mediaExtractor.getSampleTrackIndex()+"");

                try {
                    ByteBuffer inputBuffer = ByteBuffer.allocate(100 * 1024);
                    FileOutputStream fe=new FileOutputStream(file1,true);

                    while ( true) {
                        int readSampleCount = mediaExtractor.readSampleData(inputBuffer, 0);
                        if (readSampleCount < 0) {
                            break;
                        }
                        byte[] buffer = new byte[readSampleCount];
                        inputBuffer.get(buffer);
                        fe.write(buffer);
                        inputBuffer.clear();
                        mediaExtractor.advance();
                    }

                    fe.flush();
                    fe.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                }
            }

            if (mime.startsWith("video")){
                mediaExtractor.selectTrack(i);//选择此视频轨道
System.out.println("====video=====KEY_MIME==========="+format.getString(MediaFormat.KEY_MIME));             System.out.println("====video=====KEY_DURATION==========="+format.getLong(MediaFormat.KEY_DURATION)+"");
 System.out.println("====video=====KEY_WIDTH==========="+format.getInteger(MediaFormat.KEY_WIDTH)+"");              System.out.println("====video=====KEY_HEIGHT==========="+format.getInteger(MediaFormat.KEY_HEIGHT)+"");
 System.out.println("====video=====getSampleFlags==========="+mediaExtractor.getSampleFlags()+"");
 System.out.println("====video=====getSampleTime==========="+mediaExtractor.getSampleTime()+"");
// System.out.println("====video=====getSampleSize==========="+mediaExtractor.getSampleSize()+"");api28
System.out.println("====video=====getSampleTrackIndex==========="+ mediaExtractor.getSampleTrackIndex()+"");

                try {
                    ByteBuffer inputBuffer = ByteBuffer.allocate(100 * 1024);
                    FileOutputStream fe=new FileOutputStream(file2,true);

                    while ( true) {
                        int readSampleCount = mediaExtractor.readSampleData(inputBuffer, 0);
                        if (readSampleCount < 0) {
                            break;
                        }
                        byte[] buffer = new byte[readSampleCount];
                        inputBuffer.get(buffer);
                        fe.write(buffer);
                        inputBuffer.clear();
                        mediaExtractor.advance();

                    }

                    fe.flush();
                    fe.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {

                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        mediaExtractor.release();
        mediaExtractor = null;
    }
}

结果:
获取到的视频轨道信息:
getTrackCount()=2
video=KEY_MIME
=video/avc
video=KEY_DURATION
=52208333
video=KEY_WIDTH
=854
video=KEY_HEIGHT
=480
video=getSampleFlags
=1
video=getSampleTime
=83333
video=getSampleTrackIndex
===0

获取到的音频轨道信息:
audio=KEY_MIME=audio/mp4a-latm
audio=KEY_CHANNEL_COUNT
=2
audio=KEY_SAMPLE_RATE=48000
audio=KEY_DURATION
=51946666
audio=getSampleFlags=1
audio=getSampleTime
=0
audio=getSampleTrackIndex===========1

由于获取到的视频轨道的mime为video/avc,所以知道视频编码格式为.h264,获取到的音频轨道的mime为audio/mp4a-latm,对应的音频编码格式为aac。

在这里插入图片描述

上面虽然获取了视频,音频数据,但是要怎么编码,解码为常用格式呢?

代码中用到的video.mp4文件,在源码中随意找的。https://pan.baidu.com/s/1ANs0DacuTMZgnTfjKoWjtg

4 aac 和ADTS

AAC(Advanced Audio Coding)高级音频编码,有基于MPEG-2和MPEG-4标准两种音频编码技术。aac音频格式有两种编码方式ADIF和ADTS,两者之间的区别是ADIF在所有aac数据的开始添加一个ADIF头,可以确定音频数据的开始,aac文件有且只有这一个头信息。ADTS则把aac数据分成多个es帧,在每一帧的前面添加一个ADTS头信息,所以adts可以任意帧解码,因为每一帧都有头部信息。
在这里插入图片描述

上面通过MediaExtractor分离MP4文件得到的aac音频文件是没有添加adts头的原数据,所以保存的aac文件是没法播放的,只有添加了adts头才能播放。

合成的时候,MP4,flv,rtmp都不需要adts头,hls,rtp,ts是需要adts头,所以这里分离出来的aac是没有adts头信息的。想要能够播放需要手动添加adts头。

猜你喜欢

转载自blog.csdn.net/u010126792/article/details/86497571