精华内容
下载资源
问答
  • android mediaextractor实例代码
  • 介绍Android系统中提供的两个播放器模块MediaExtractor 和MediaCodec的简单使用,利用他们来完成一个简易的播放器。 其中MediaExtractor完成解复用工作,而MediaCodec则音视频解码工作。

    对于一个播放器,基本上可以分为以下模块:数据接收(网络/本地)->解复用->音视频解码->音视频同步->音视频输出。
    今天我们介绍Android系统中提供的两个播放器模块MediaExtractor 和MediaCodec的简单使用,利用他们来完成一个简易的播放器。
    其中MediaExtractor完成解复用工作,而MediaCodec则完成音视频解码工作。
    在这里插入图片描述

    1、MediaExtractor简介

    MediaExtractor主要负责解复用工作,在我们的简易播放其中,有以下两个作用:
    1、获取媒体文件的格式,包括音视频轨道,编码格式、宽、高、采样率、声道数等等。
    2、分离音频流、视频流,读取分离后的音视频数据。

    MediaExtractor模块使用比较简单,但存在以下不足:

    • 支持格式较少;
    • 对于网络流的支持十分有限,大部分平台上支持http,不包括hls
    • 无法从内存中读取,或者是通过buffer写入。

    ps: 这些原因让我会感觉这个模块比FFmpeg逊色不少,特别是无法像FFmpeg那样从内存中读取,导致要扩展一个网络流(如rtsp)十分麻烦,有以下两种方式:
    1、 继承Android提供的DataSource类实现,但该类目前Android未开放。
    2、 从framework层扩展支持。

    使用步骤及关键接口:

    1、设置数据源,即可以设置本地文件又可以设置网络文件,仅http。一般使用以下接口,实际上还可以传入Uri或者FileDescriptor。

      void setDataSource(String path) 
    

    2、获取媒体文件音视频轨道数。

    int getTrackCount();//返回值为轨道数
    

    3、遍历所有轨道,获取音视频格式

    MediaFormat getTrackFormat(int index)//获取指定index的音视频格式
    

    4、选定一跳音频或者视频轨道,这样后面从MediaExtractor读取数据就只会从该轨道中读取

    void selectTrack(int index);
    

    5、读取数据

    int readSampleData(ByteBuffer byteBuf, int offset)
    

    ByteBuffer为MediaExtractor从指定轨道中解复用出来的数据;
    返回值为-1表示已全部读完。

    6、跳转到下一个数据

    boolean advance();
    

    返回值为false表示已全部读完

    7、释放资源

    void release()
    

    8、其他

    • 获取时间戳,单位为微秒
      long getSampleTime()

    特别说明的是Android提供了不开放的接口setDataSource(DataSource source)及DataSource类,是在MediaExtractor上拓展流媒体最简便的方式。
    DataSource类(Android不开放):

    package android.media;
    
    import java.io.Closeable;
    
    /**
     * An abstraction for a media data source, e.g. a file or an http stream
     * {@hide}
     */
    public interface DataSource extends Closeable {
        /**
         * Reads data from the data source at the requested position
         *
         * @param offset where in the source to read
         * @param buffer the buffer to read the data into
         * @param size how many bytes to read
         * @return the number of bytes read, or -1 if there was an error
         */
        public int readAt(long offset, byte[] buffer, int size);
    
        /**
         * Gets the size of the data source.
         *
         * @return size of data source, or -1 if the length is unknown
         */
        public long getSize();
    }
    

    自己扩展DataSource类,可以完成自定义的媒体文件获取方式,主要还是用在流媒体。如扩展从文件读取类,MyDataSource:

    public class MyDataSource implements DataSource {
    	
      MyDataSource(String url){	 
        try {        
            mFile = new File(url);
            mSize = mFile.length();
        } catch (Exception e1) {  
            e1.printStackTrace();  
        } 
      }	
    
      private static final String TAG = "testMediaCodec";
      private long mSize = 0;
      private File mFile = null;
    
      public int readAt(long offset, byte[] buffer, int size){
        int bytes = 0;
        InputStream in = null;
        try {  
            in = new FileInputStream(mFile); 
            in.skip(offset);//注意offset是对整个文件的偏移量
            bytes   = in.read(buffer, 0, size);   
        } catch (Exception e1) {  
            e1.printStackTrace();  
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        return bytes;
      }
      
      public long getSize(){	  
        return mSize;
      }
    
      public void close() throws IOException{ }
    }
    
    

    2、MediaCodec简介

    MediaCodec类可用于编解码。通常与MediaExtractor(解复用器)、MediaMuxer(复用器)、AudioTrack(音频播放接口)结合使用。

    需要注意的是MediaCodec并非是编解码器,它是Android封装的API,提供给应用层使用,可用于访问Android底层的多媒体编解码器,这些编解码器基于OMX框架。

    MediaCodec是如何使用中OMX的?基本框架如下图所示:
    在这里插入图片描述
    1)MediaCodec native层使用的codec类为ACodec类;
    2)ACodec通过binder(IOMX)访问OMX适配层;
    3)OMX适配层封装OpenMax IL层,底层编解码库对接OpenMax IL层后嵌套到该层。

    工作流程

    MediaCodec采用异步方式处理数据,并且使用了一组输入输出缓存(input and output buffers)来存放待处理数据以及处理完的数据。开发者只需将待编解码的数据放入输入缓冲区交给编解码器,再从输出缓冲区获取编解码后的数据即可。

    其工作方式大致如下:
    1、请求或接收一个空的输入缓存(input buffer)。
    2、向其中填充待处理的数据,并将它传递给编解码器处理。
    3、MediaCodec处理完这些数据并将处理结果输出至一个空的输出缓存(output buffer)中。
    4、请求或接收到一个填充了处理后数据的输出缓存(output buffer)。
    5、使用完其中的数据,并将其释放给编解码器再次使用。
    如下图所示:
    MediaCodec处理流程

    MediaCodec主要的状态为:Stopped、Executing、Released。

    • Stopped的状态下也分为三种子状态:Uninitialized、Configured、Error。
    • Executing的状态下也分为三种子状态:Flushed、 Running、End-of-Stream。

    MediaCodec状态变换图如下:
    在这里插入图片描述

    1、当创建编解码器的时候处于未初始化状态。首先需要调用configure(…)方法进入Configured状态,然后调用start()方法让其处于Executing状态。在Executing状态下,就可以缓冲区来处理数据。

    2、Executing的状态下也分为三种子状态:Flushed、 Running、End-of-Stream。在start() 调用后,编解码器处于Flushed状态,这个状态下它保存着所有的缓冲区。一旦第一个输入buffer出现了,编解码器就会自动运行到Running的状态。当带有end-of-stream标志的buffer进去后,编解码器会进入End-of-Stream状态,这种状态下编解码器不在接受输入buffer,但是仍然在产生输出的buffer。此时可以调用flush()方法,将编解码器重置于Flushed状态。

    3、调用stop()可以将编解码器返回到Uninitialized状态,然后可以重新配置。

    4、在底层编解码出错的情况下,MediaCodec会转到错误状态。调用reset()使编解码器再次可用,reset()可以从任何状态将编解码器移Uninitialized状态。

    5、当MediaCodec数据处理任务完成时或不再需要MediaCodec时,可使用release()方法释放其资源,到达Released状态。

    使用步骤及关键接口:

    1、创建MediaCodec。
    name指编解码器名字,type指的是MIME类型,支持的name和type具体可见/system/etc/ media_codecs.xml。

    MediaCodec createByCodecName(String name) 
    MediaCodec createDecoderByType(String type) 
    MediaCodec createEncoderByType(String type)
    

    2、configure配置编解码器。
    format可以用来设置一些属性,如视频的宽,高,帧率,音频的声道,采样率等。surface用于解码器把解码后的视频帧直接显示到屏幕,注意,设置这个参数后,解码出来的ByteBuffers将无法获取到数据,因为解码器为了提高效率并没有把数据copy到ByteBuffer中,但是可以根据ByteBuffer的id,通过getOutputImage(int index)把帧数据取出来。 crypto与解密相关,暂时没有研究。Flags指定当前的是编码器还是解码器,编码器需要使用CONFIGURE_FLAG_ENCODE。

    void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
    

    3、启动编解码器,通知编解码器开始工作,在configure后调用。

    void start()
    

    4、进入编解码处理,对应上面流程图:

    1)请求一块空闲的输入缓冲区ByteBuffers。
    timeoutUs是超时时长,0表示立即返回,-1表示一直等待,其他时间表示等待多少微秒,如果设置为-1或者设置了一个过长的值,有可能会直接导致线程卡住。
    返回值是ByteBuffer的索引值。使用索引通过 getInputBuffer(int index)或者数组下标的方式获取到对应ByteBuffers。

    int dequeueInputBuffer (long timeoutUs)
    

    2)获取要处理的数据,并将其填充到输入缓冲区(ByteBuffer),提交给编解码器处理。
    index是ByteBuffer的索引值,与dequeueInputBuffer返回值对应;
    offset是有效数据在buffer中的偏移量;
    size是有效数据的长度;
    presentationTimeUs是当前数据的时间戳,单位是微秒;
    flags是一些标记的位掩码,用于通知编解码当前数据是流结束BUFFER_FLAG_END_OF_STREAM ,编解码需要的Codec-specific数据BUFFER_FLAG_CODEC_CONFIG等等。

    void queueInputBuffer (int index, int offset, int size, long presentationTimeUs, int flags)
    

    3)queueInputBuffer 后 MediaCodec会对数据进行处理。
    4)获取处理后的数据。
    info包含了处理结束后的数据以及一些标记值;
    timeoutUs是超时时长,0表示立即返回,-1表示一直等待,其他时间表示等待多少微秒,如果设置为-1或者设置了一个过长的值,有可能会直接导致线程卡住。
    返回值是ByteBuffer的索引值。使用索引通过 getOutputBuffer(int index)或者数组下标的方式获取到对应ByteBuffers。返回值小于0表示一些错误信息包括输出格式改变,输出buffer改变,稍后重新尝试等等。

    int dequeueOutputBuffer (MediaCodec.BufferInfo info, long timeoutUs)
    

    5)释放一个输出ByteBuffers,还给编解码器。
    Index是buffer的索引,与dequeueOutputBuffer 获取到的索引向对应;
    Render表示是否需要在surface中显示;
    renderTimestampNs表示在surface中显示并且设置时间戳。Android 4.0及以前版本没有第二个接口。

    void releaseOutputBuffer (int index, boolean render)
    void releaseOutputBuffer (int index, long renderTimestampNs)
    

    5、停止编解码器,通知编解码器停止工作,与start相对。

    void stop ()
    

    6、释放编解码器及其占用资源

    void release()
    

    7、重置编解码器

    void reset()
    

    3、构建简单播放器

    初始化MediaExtractor,遍历所有音视频轨道,创建音频MediaCodec和视频MediaCodec,并完成config。

    public int prepare(){
    		for(int i=0 ;i < mVExtractor.getTrackCount(); i++){
    			MediaFormat format = mVExtractor.getTrackFormat(i);
    			Log.d(TAG, ">> format" + i + ": " +  format);
                String mime = format.getString(MediaFormat.KEY_MIME);
                Log.d(TAG, ">> mime i " + i + ": " +  mime);
                
                if (mime.startsWith("audio/")){
                	mAExtractor.selectTrack(i);
                	mAMediaCodec = MediaCodec.createDecoderByType(mime);
                	mAMediaCodec.configure(format, null, null, 0); 
                	
                	int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                    int channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 
                	int buffsize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
                	mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 
                			sampleRate*channels/2,	//fix to STEREO, so sample-rate maybe change
                			AudioFormat.CHANNEL_OUT_STEREO,
                            AudioFormat.ENCODING_PCM_16BIT, 
                            buffsize*10, 
                            AudioTrack.MODE_STREAM);
                }
                
                if (mime.startsWith("video/")){
                	mVExtractor.selectTrack(i);
                	mVMediaCodec = MediaCodec.createDecoderByType(mime);
                	mVMediaCodec.configure(format, mSurface, null, 0);            	
                }
    		}
    		
    		return 1 ;
    	}
    

    视频解码线程,不停读取MediaExtractor从视频轨道中分离的视频数据,放入inputbuffer给视频MediaCodec解码,得到解码后数据OutputBuffer,MediaCodec会将其渲染到surface。
    没有做音视频同步,仅简单通过视频PTS调整视频播放速率,避免太快。

    如何进行音视频同步,参考之前的博客 https://blog.csdn.net/myvest/article/details/97416415

    数据读完时,将BUFFER_FLAG_END_OF_STREAM给到解码器,结束线程。

    class VDecodeThread extends Thread{
    	    @Override
    	    public void run() {					
    			long timeout = 10000;//10ms
    			long startMs = System.currentTimeMillis();
    			boolean isEOS = false;
    			MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
    			
    			mVMediaCodec.start();
    			ByteBuffer[] inBuffers =  mVMediaCodec.getInputBuffers();
    			//ByteBuffer[] outBuffers = mMediaCodec.getOutputBuffers();
    
    			while(!isEOS){
    				int inIndex = mVMediaCodec.dequeueInputBuffer(timeout);
    				if(inIndex >= 0){
    					int size = mVExtractor.readSampleData(inBuffers[inIndex], 0);//demux get video es
    					if(size < 0){
                            Log.d(TAG, "mybe eos or error");
                            mVMediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
    					}else{
    						mVMediaCodec.queueInputBuffer(inIndex, 0, size, mVExtractor.getSampleTime(), 0);
    						mVExtractor.advance();
    						inBuffers[inIndex].clear();
    					}
    				}				
    
    				int outIndex = -1;
    				do{
    					outIndex = mVMediaCodec.dequeueOutputBuffer(outBufferInfo, timeout);
    					if((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
    						Log.d(TAG, "outBufferInfo flag is BUFFER_FLAG_END_OF_STREAM");
    						isEOS = true;
    					}
    					
    					if(outIndex >= 0){
    	                    // We use a very simple clock to keep the video FPS, or the video
    	                    // playback will be too fast
    	                    while (outBufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
    	                        try {
    	                            sleep(10);
    	                        } catch (InterruptedException e) {
    	                            e.printStackTrace();
    	                            break;
    	                        }
    	                    } 
    	                    mVMediaCodec.releaseOutputBuffer(outIndex, true);  //true means output to surface
    					}
    				}while(outIndex >= 0);
    				 
    			}				
    			releaseVideo();
    	    }
    	};
    

    音频解码线程和视频类似,不过需要自己处理解码后的数据,我们采用AudioTrack进行播放,需要注意的是,prepare阶段创建AudioTrack时,是固定为2声道,那么需要进行简单的声道、采样率调整处理,否则有些流声道数不为2会播放太快/太慢。

    AudioTrack的使用参考之前的博客 https://blog.csdn.net/myvest/article/details/90731805

    mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 
                			sampleRate*channels/2,	//fix to STEREO, so sample-rate maybe change
                			AudioFormat.CHANNEL_OUT_STEREO,
                            AudioFormat.ENCODING_PCM_16BIT, 
                            buffsize*10, 
                            AudioTrack.MODE_STREAM);
    

    音频解码代码如下:

    class ADecodeThread extends Thread{
    	    @Override
    	    public void run() {					
    			long timeout = 1000;//1ms	
    			boolean isEOS = false;
    			MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
    			
    			mAMediaCodec.start();
    			ByteBuffer[] inBuffers =  mAMediaCodec.getInputBuffers();
    			ByteBuffer[] outBuffers = mAMediaCodec.getOutputBuffers();
    
    			mAudioTrack.play();
    			byte[] data = null;
    			
    			while(!isEOS){
    				int inIndex = mAMediaCodec.dequeueInputBuffer(timeout);
    				if(inIndex >= 0){
    					int size = mAExtractor.readSampleData(inBuffers[inIndex], 0);//demux get audio es
    					if(size < 0){
                            Log.d(TAG, "mybe eos or error");
                            mAMediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
    					}else{
    						mAMediaCodec.queueInputBuffer(inIndex, 0, size, mAExtractor.getSampleTime(), 0);
    						mAExtractor.advance();
    						inBuffers[inIndex].clear();
    					}
    				}
    				
    
    				int outIndex = -1;
    				do{
    					outIndex = mAMediaCodec.dequeueOutputBuffer(outBufferInfo, timeout);
    					if((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
    						Log.d(TAG, "outBufferInfo flag is BUFFER_FLAG_END_OF_STREAM");
    						isEOS = true;
    					}					
                        
    					if(outIndex >= 0){
    						
    						if(outBufferInfo.size > 0){
    							ByteBuffer outBuf = outBuffers[outIndex];
                                outBuf.position(outBufferInfo.offset);
                                outBuf.limit(outBufferInfo.offset + outBufferInfo.size); 
                                if(data == null)
                                	data = new byte[outBufferInfo.size];
                                Arrays.fill(data, (byte) 0);
                                outBuf.get(data);
                                mAudioTrack.write(data, 0, outBufferInfo.size);//output to audio track
                                outBuf.clear();
                                
    						}
    							
    	                    mAMediaCodec.releaseOutputBuffer(outIndex, false); 
    					}
    				}while(outIndex >= 0);				 
    			}
    			data = null;
    			releaseAudio();
    	    }
    	};
    
    展开全文
  • 我们知道多媒体文件当中包含很多流,比如视频流,音频流等。...import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Bundle; import android.util.Log; import android.view

    我们知道多媒体文件当中包含很多流,比如视频流,音频流等。我们可以通过

    MediaExtractor获取到相关的信息。下面是具体用法。
    package com.yuanxuzhen.androidmedia.demux;
    
    import android.media.MediaExtractor;
    import android.media.MediaFormat;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import com.yuanxuzhen.androidmedia.DirUtil;
    import com.yuanxuzhen.androidmedia.databinding.ActivityDemuxBinding;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class DemuxActivity extends AppCompatActivity {
        ActivityDemuxBinding mainBinding;
        ExecutorService mExecutorService;
        private String mediaPath;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mainBinding = ActivityDemuxBinding.inflate(getLayoutInflater());
            setContentView(mainBinding.getRoot());
            mExecutorService = Executors.newCachedThreadPool();
            mediaPath = DirUtil.getCacheDir() + File.separator + "in.mp4";
            mainBinding.print.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mExecutorService.execute(new Runnable() {
                        @Override
                        public void run() {
                              /*打印通道信息*/
    //                        printMediaCount(mediaPath);
    //                        int videoIndex = getTrackIndex("video", mediaPath);
    //                        printLog("videoIndex="+videoIndex);
                            printVideoMeidaInfo(mediaPath);
                            printAudioMeidaInfo(mediaPath);
                        }
                    });
                }
            });
        }
    
        private void printLog(String msg){
            Log.e("DemuxAcYuan", msg);
        }
        private  void printMediaCount(String path){
            try{
                MediaExtractor extractor = new MediaExtractor();//实例一个MediaExtractor
                extractor.setDataSource(path);
                int count = extractor.getTrackCount();//获取轨道数量
                printLog("printMediaInfo count="+count);
                for (int i = 0; i < count; i++){
                    MediaFormat mediaFormat = extractor.getTrackFormat(i);
                    printLog(i+"编号通道格式 = "+mediaFormat.getString(MediaFormat.KEY_MIME));
                }
    
            }catch (Exception e){
                e.printStackTrace();
            }
    
        }
    
        private int getTrackIndex(String targetTrack, String path) {
            MediaExtractor extractor = new MediaExtractor();//实例一个MediaExtractor
            try {
                extractor.setDataSource(path);//设置添加MP4文件路径
            } catch (IOException e) {
                e.printStackTrace();
            }
            int trackIndex = -1;
            int count = extractor.getTrackCount();//获取轨道数量
            for (int i = 0; i < count; i++) {
                MediaFormat mediaFormat = extractor.getTrackFormat(i);
                String currentTrack = mediaFormat.getString(MediaFormat.KEY_MIME);
                if (currentTrack.startsWith(targetTrack)) {
                    trackIndex = i;
                    break;
                }
            }
            return trackIndex;
    
        }
    
    
        private void printVideoMeidaInfo(String path){
            int index = getTrackIndex("video", path);
            if(index < 0){
                return;
            }
            try{
                MediaExtractor extractor = new MediaExtractor();//实例一个MediaExtractor
                extractor.setDataSource(path);
                MediaFormat mediaFormat = extractor.getTrackFormat(index);
                VideoMediaInfo videoMediaInfo = new VideoMediaInfo();
                videoMediaInfo.language = mediaFormat.getString(MediaFormat.KEY_LANGUAGE);//获取语言格式内容
                videoMediaInfo.width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
                videoMediaInfo.height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
                videoMediaInfo.durationTime = mediaFormat.getLong(MediaFormat.KEY_DURATION);
    
                videoMediaInfo.maxByteCount = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取视频缓存输出的最大大小
                try{
                    Integer bitRate = mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE);//获取比特
                }catch (Exception e){
                    e.printStackTrace();
                }
    
                try{
                    videoMediaInfo.colorFormat = mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);//颜色格式
                }catch (Exception e){
                    e.printStackTrace();
                }
    
                try{
                    videoMediaInfo.frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);//帧率
    
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    videoMediaInfo. tileWidth = mediaFormat.getInteger(MediaFormat.KEY_TILE_WIDTH);//图块分辨率
    
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    videoMediaInfo. tileHeight = mediaFormat.getInteger(MediaFormat.KEY_TILE_HEIGHT);//图块分辨率
    
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    videoMediaInfo.gridRows = mediaFormat.getInteger(MediaFormat.KEY_GRID_ROWS);//网格行
    
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    videoMediaInfo.gridColumns = mediaFormat.getInteger(MediaFormat.KEY_GRID_COLUMNS);//网格列
    
                }catch (Exception e){
                    e.printStackTrace();
                }
    
                printLog("printVideoMeidaInfo " + videoMediaInfo);
    
            }catch (Exception e){
                e.printStackTrace();
            }
    
    
        }
    
    
        private void printAudioMeidaInfo(String path){
            int index = getTrackIndex("audio", path);
            if(index < 0){
                return;
            }
            try{
                MediaExtractor extractor = new MediaExtractor();//实例一个MediaExtractor
                extractor.setDataSource(path);
                MediaFormat mediaFormat = extractor.getTrackFormat(index);
                AudioMediaInfo mediaINfo = new AudioMediaInfo();
                try{
                     mediaINfo.bitRate = mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE);//获取比特
                }catch (Exception e){
                    e.printStackTrace();
                }
    
                try{
    //                mediaINfo.pcmEncoding = mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);//PCM-编码 模拟信号编码
                    mediaINfo.sampleRate =  mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                            mediaINfo.channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                }catch (Exception e){
                    e.printStackTrace();
                }
    
    
                try{
                    mediaINfo.isAdts = mediaFormat.getInteger(MediaFormat.KEY_IS_ADTS);
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    mediaINfo.keyChannelMask = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_MASK);//图块分辨率
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    mediaINfo.aacProfile = mediaFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);//图块分辨率
                }catch (Exception e){
                    e.printStackTrace();
                }
    
    
                printLog("printAudioMeidaInfo " + mediaINfo);
    
            }catch (Exception e){
                e.printStackTrace();
            }
    
    
        }
    
    
    
    }
    package com.yuanxuzhen.androidmedia.demux;
    
    public class VideoMediaInfo {
        public String language; //语言
        public int width; //视频宽
        public int height; //视频高
        public long durationTime;//视频时间
        public int maxByteCount;
        public int colorFormat;
        public int frameRate;
        public int tileWidth;
        public int  tileHeight;
        public int gridRows;
        public int  gridColumns;
    
        @Override
        public String toString() {
            return "VideoMediaInfo{" +
                    "language='" + language + '\'' +
                    ", width=" + width +
                    ", height=" + height +
                    ", durationTime=" + durationTime +
                    ", maxByteCount=" + maxByteCount +
                    ", colorFormat=" + colorFormat +
                    ", frameRate=" + frameRate +
                    ", tileWidth=" + tileWidth +
                    ", tileHeight=" + tileHeight +
                    ", gridRows=" + gridRows +
                    ", gridColumns=" + gridColumns +
                    '}';
        }
    }
    
    package com.yuanxuzhen.androidmedia.demux;
    
    public class AudioMediaInfo {
        int bitRate;
        int pcmEncoding;
        int sampleRate;
        int channelCount;
        int isAdts;
        int keyChannelMask;
        int aacProfile;
    
    
        @Override
        public String toString() {
            return "AudioMediaInfo{" +
                    "bitRate=" + bitRate +
                    ", pcmEncoding=" + pcmEncoding +
                    ", sampleRate=" + sampleRate +
                    ", channelCount=" + channelCount +
                    ", isAdts=" + isAdts +
                    ", keyChannelMask=" + keyChannelMask +
                    ", aacProfile=" + aacProfile +
                    '}';
        }
    }
    

    gitee地址

    https://gitee.com/creat151/android-media.git

    展开全文
  • String) { try { val mediaExtractor = MediaExtractor() mediaExtractor.setDataSource(fileStr) var audioIndex = -1 var hasAudio = false for (i in 0 until mediaExtractor.trackCount) { var format: Media...
    
    //activity 1
    class Video2PcmActivity : Activity() {
        val TAG: String = "Video2PcmActivity"
        val filePath: String = "/sdcard/test/test.mp4"
        val pcmPath: String = "/sdcard/test/test.pcm"
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_pcm)
            Thread {
                try {
                    initExtractor(filePath)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }.start()
        }
    
        private fun initExtractor(fileStr: String) {
            try {
                val mediaExtractor = MediaExtractor()
                mediaExtractor.setDataSource(fileStr)
                var audioIndex = -1
                var hasAudio = false
                for (i in 0 until mediaExtractor.trackCount) {
                    var format: MediaFormat = mediaExtractor.getTrackFormat(i)
                    var mine: String = format.getString(MediaFormat.KEY_MIME)
                    if (mine.startsWith("audio")) {
                        audioIndex = i
                        hasAudio = true
                        break
                    }
                }
                if (hasAudio) {
                    mediaExtractor.selectTrack(audioIndex)
                    Thread {
                        getPCMfromMp4(mediaExtractor, audioIndex)
                        Log.e(TAG,"finish")
                    }.start()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
        /**
         * 单纯得到PCM文件
         */
        private fun getPCMfromMp4(extractor: MediaExtractor, index: Int) {
            try {
                val mediaFormat: MediaFormat = extractor.getTrackFormat(index)
                val audioCodec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME))
                audioCodec.configure(mediaFormat, null, null, 0)
                audioCodec.start()
    
                val inputBuff = audioCodec.inputBuffers
                val outputBuff = audioCodec.outputBuffers
    
                val inputInfo = MediaCodec.BufferInfo()
                val outputInfo = MediaCodec.BufferInfo()
                val fileOutPutStream = FileOutputStream(pcmPath)
                var inputDone = false
                var isOverCode = false
                while (!isOverCode) {
                    if (!inputDone) {
                        for (i in 0 until inputBuff.size) {
                            val inputIndex = audioCodec.dequeueInputBuffer(0)
                            if (inputIndex >= 0) {
                                val byteBuffer = inputBuff[inputIndex]
                                byteBuffer.clear()
                                val sampleSize = extractor.readSampleData(byteBuffer, 0)
                                if (sampleSize <= 0) {
                                    audioCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                                    inputDone = true
                                } else {
                                    inputInfo.offset = 0
                                    inputInfo.size = sampleSize
                                    inputInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME
                                    inputInfo.presentationTimeUs = extractor.sampleTime
                                    audioCodec.queueInputBuffer(
                                        inputIndex,
                                        inputInfo.offset,
                                        inputInfo.size,
                                        inputInfo.presentationTimeUs,
                                        0
                                    )
                                    extractor.advance()
                                }
                            }
                        }
                    }
                    var decodeOutPutDone = false
                    while (!decodeOutPutDone) {
                        val outPutIndex = audioCodec.dequeueOutputBuffer(outputInfo, 0)
                        if (outPutIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                            decodeOutPutDone = true
                        } else if (outPutIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || outPutIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    
                        } else if (outPutIndex < 0) {
    
                        } else {
                            val dataBuff: ByteBuffer = outputBuff[outPutIndex]
                            val chumkPcm = ByteArray(outputInfo.size)
                                dataBuff.get(chumkPcm)
                                dataBuff.clear()
                                fileOutPutStream.write(chumkPcm)
                                fileOutPutStream.flush()
                            audioCodec.releaseOutputBuffer(outPutIndex, false)
                            if ((outputInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                                extractor.release()
                                audioCodec.stop()
                                audioCodec.release()
                                decodeOutPutDone = true
                                isOverCode = true
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
        }
    }
    
    //activity 2
    class PlayPcmActivity : Activity() {
        val pcmPath: String = "/sdcard/test/test.pcm"
        val streamType: Int = AudioManager.STREAM_MUSIC
        val streamRate: Int = 44100
        val channelConfig: Int = AudioFormat.CHANNEL_CONFIGURATION_MONO
        val audioFormat: Int = AudioFormat.ENCODING_PCM_16BIT
        val model: Int = AudioTrack.MODE_STREAM
        var buffSize: Int = 0
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_pcm)
            playPcm()
        }
    
        private fun playPcm() {
            try {
                buffSize = AudioTrack.getMinBufferSize(streamRate, channelConfig, audioFormat)
                val track: AudioTrack = AudioTrack(streamType, streamRate, channelConfig, audioFormat, buffSize, model)
                Thread {
                    track.play()
                    val filtInputStream: FileInputStream = FileInputStream(pcmPath)
                    var resultArray: ByteArray = ByteArray(buffSize)
                    while (filtInputStream.read(resultArray) != -1) {
                        track.write(resultArray, 0, resultArray.size)
                    }
                    filtInputStream.close()
                    track.stop()
                    track.release()
                }.start()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    
    

     

    展开全文
  • Android MediaExtractor

    2019-06-21 23:30:58
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent...
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/btn_record_media"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="开始录制视频" />
    
        <Button
            android:id="@+id/btn_extract_media"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="开始分离视频文件" />
    
        <TextView
            android:id="@+id/tv_video_size"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/tv_audio_size"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    
    package com.example.media.codec;
    
    import android.annotation.TargetApi;
    import android.content.Intent;
    import android.media.MediaExtractor;
    import android.media.MediaFormat;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Environment;
    import android.provider.MediaStore;
    import android.support.annotation.Nullable;
    import android.support.v7.app.AppCompatActivity;
    import android.text.format.Formatter;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.example.media.R;
    
    import java.io.Closeable;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    
    public class MediaExtractorActivity extends AppCompatActivity {
        private static final String TAG = MediaExtractorActivity.class.getSimpleName();
    
        private Button mBtnExtractMedia;
        private Button mBtnRecordMedia;
        private TextView mTvVideoSize;
        private TextView mTvAudioSize;
    
        private Uri mTargetVideoUri;
        private File mExtractVideoFile;
        private File mExtractAudioFile;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_media_extractor);
    
            mTvVideoSize = findViewById(R.id.tv_video_size);
            mTvAudioSize = findViewById(R.id.tv_audio_size);
    
            mBtnRecordMedia = findViewById(R.id.btn_record_media);
            mBtnRecordMedia.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mTargetVideoUri != null) {
                        Log.v(TAG, "target video file exists, delete file and recapture "
                                + getContentResolver().delete(mTargetVideoUri, null, null));
                    }
    
                    mBtnRecordMedia.setEnabled(false);
                    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
                    startActivityForResult(intent, 0);
                }
            });
    
            mBtnExtractMedia = findViewById(R.id.btn_extract_media);
            mBtnExtractMedia.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        Log.v(TAG, "external storage no exists");
                        return;
                    }
    
                    mBtnExtractMedia.setEnabled(false);
                    mBtnExtractMedia.setText("正在分离中.....");
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        extractMedia();
                    }
                }
            });
    
            mBtnRecordMedia.setEnabled(true);
            mBtnExtractMedia.setEnabled(false);
        }
    
        @Override
        protected void onDestroy() {
            if (mTargetVideoUri != null) {
                Log.v(TAG, "target video file is deleted ? "
                        + getContentResolver().delete(mTargetVideoUri, null, null));
            }
            if (mExtractVideoFile != null && mExtractVideoFile.exists()) {
                Log.v(TAG, "extract video file is deleted ? " + mExtractVideoFile.delete());
            }
            if (mExtractAudioFile != null && mExtractAudioFile.exists()) {
                Log.v(TAG, "extract audio file is deleted ? " + mExtractAudioFile.delete());
            }
            super.onDestroy();
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            mBtnRecordMedia.setEnabled(true);
            mBtnExtractMedia.setEnabled(true);
    
            if (resultCode == RESULT_OK && data != null) {
                Uri uri = data.getData();
                if (uri != null) {
                    mTargetVideoUri = uri;
                }
            }
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        private void extractMedia() {
            if (mTargetVideoUri == null) {
                Log.v(TAG, "extract video file no exists");
                mBtnExtractMedia.setEnabled(true);
                mBtnExtractMedia.setText("开始分离视频文件");
                return;
            }
    
            String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() +
                    "/Android/data/" + getPackageName() + "/audio/";
            File rootDir = new File(rootPath);
            if (!rootDir.exists()) {
                if (!rootDir.mkdirs()) {
                    Log.v(TAG, "root dir create failed");
                    mBtnExtractMedia.setEnabled(true);
                    mBtnExtractMedia.setText("开始分离视频文件");
                    return;
                }
            }
    
            // setDataSource(String filePath):设置本地文件位置或网络url
    
            // 分离轨道相关方法:
            // getTrackCount():获取轨道数量
            // getTrackFormat(int index):获取对应轨道的信息。如该轨道的音频/视频格式等
            // selectTrack(int track):选择轨道,选择的轨道只有第一次有效,往后的选择的轨道无效
    
            // 读取数据相关方法:
            // readSampleData(ByteBuffer byteBuffer, int offset):将数据读取到ByteBuffer中。返回-1表示没有更多数据
            // advance():跳到下一个数据包,如果没有下一个返回false
    
            // release():释放MediaExtractor
    
            MediaExtractor mediaExtractor = null;
            FileOutputStream videoOutputStream = null;
            FileOutputStream audioOutputStream = null;
            int videoTrackIndex = -1;
            int audioTrackIndex = -1;
            try {
                // 创建存放分离的音频和视频文件
                // (分离的文件不能播放,一般是MediaExtractor分离音频轨和视频轨后,分别使用MediaCodec再进行编解码生成文件)
                String extractDate = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA).format(new Date());
                mExtractVideoFile = new File(rootPath + "extract_video" + extractDate + ".mp4");
                mExtractAudioFile = new File(rootPath + "extract_audio" + extractDate + ".aac");
                if (!mExtractVideoFile.createNewFile()) {
                    Log.v(TAG, "create extract video file failed");
                    return;
                }
                if (!mExtractAudioFile.createNewFile()) {
                    Log.v(TAG, "create extract audio file failed");
                    return;
                }
                videoOutputStream = new FileOutputStream(mExtractVideoFile);
                audioOutputStream = new FileOutputStream(mExtractAudioFile);
    
                mediaExtractor = new MediaExtractor();
                mediaExtractor.setDataSource(this, mTargetVideoUri, null); // 设置本地或远程数据源
                int trackCount = mediaExtractor.getTrackCount(); // 获取分离的轨道数量
                for (int i = 0; i < trackCount; i++) {
                    MediaFormat format = mediaExtractor.getTrackFormat(i);
                    String mimeType = format.getString(MediaFormat.KEY_MIME);
                    if (mimeType.startsWith("video")) {
                        videoTrackIndex = i;
                    }
                    if (mimeType.startsWith("audio")) {
                        audioTrackIndex = i;
                    }
                }
    
                ByteBuffer inputBuffer = ByteBuffer.allocate(500 * 1024);
                // 分离视频轨
                mediaExtractor.selectTrack(videoTrackIndex); // 选择视频轨
                while (mediaExtractor.readSampleData(inputBuffer, 0) != -1) {
                    byte[] buffer = new byte[mediaExtractor.readSampleData(inputBuffer, 0)];
                    inputBuffer.get(buffer);
                    videoOutputStream.write(buffer);
                    inputBuffer.clear();
                    mediaExtractor.advance(); // 写入文件后,跳到下一帧
                }
                // 分离音频轨
                mediaExtractor.selectTrack(audioTrackIndex); // 选择音频轨
                while (mediaExtractor.readSampleData(inputBuffer, 0) != -1) {
                    byte[] buffer = new byte[mediaExtractor.readSampleData(inputBuffer, 0)];
                    inputBuffer.get(buffer);
                    audioOutputStream.write(buffer);
                    inputBuffer.clear();
                    mediaExtractor.advance();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                Toast.makeText(this, "FileNotFoundException", Toast.LENGTH_SHORT).show();
                finish();
            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(this, "IOException", Toast.LENGTH_SHORT).show();
                finish();
            } finally {
                closeSilently(videoOutputStream);
                closeSilently(audioOutputStream);
                if (mediaExtractor != null) {
                    mediaExtractor.release();
                }
                showExtractSize(mExtractVideoFile, mExtractAudioFile);
            }
        }
    
        private void showExtractSize(File extractVideoFile, File extractAudioFile) {
            if (extractVideoFile.exists() && extractAudioFile.exists()) {
                String videoSize = "分离的视频轨大小:"
                        + Formatter.formatFileSize(this, extractVideoFile.length());
                String audioSize = "分离的音频轨大小:"
                        + Formatter.formatFileSize(this, extractAudioFile.length());
                mTvVideoSize.setText(videoSize);
                mTvAudioSize.setText(audioSize);
    
                mBtnExtractMedia.setEnabled(true);
                mBtnExtractMedia.setText("分离结束");
            }
        }
    
        private void closeSilently(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    
    展开全文
  • Android MediaExtractor setDataSource

    千次阅读 2015-09-01 20:04:04
    MediaExtractor(java)file: frameworks/base/media/java/android/mediaf/MediaExtractor.java 目前Android 5.0仅支持本地视频 public final void setDataSource(FileDescriptor fd) throws IOExceptio
  • Android使用MediaExtractor读取媒体信息 Refrence Android 判断字符串是否为url 用来测试的在线小视频url地址 Source Code public class BottomActivity extends AppCompatActivity { private static final ...
  • import android.media.MediaExtractor; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; public class ExecutorAudio { public void extractAudio...
  • file: frameworks/base/media/Java/Android/media/MediaExtractor.java class: MediaExtractor MediaExtractor上层接口。 其中有3个调用JNI的接口: private static native final void native_init(); pri
  • MediaExtractor setDataSource了一个flv文件(分辨率2560*1440) <br/> 通过getTrackFormat获取MediaFormat.KEY_MIME为video后 <br/> MediaFormat.KEY_MAX_INPUT_SIZE的大小为230400,通过...
  • Android-MediaExtractor详解

    千次阅读 2019-07-31 20:47:01
    1. MediaExtractor,即视频解析,主要作用是音视频分离,解析头信息,分别获取音视频流。 2. MediaCodec,即对音视频流进行解码,获取pcm和yuv数据。详见: ...
  • 写在前面:学习Android多媒体的步骤: 1,Audio PCM &video YUV各种数据的处理,格式的封装与装换原理 2,多媒体的播放框架,nuplayer ,stagefright 3,音视频分离 MediaExtractor 4,音频编解码(以AAC为例) ...
  • MediaExtractor,用于提取指定媒体文件的媒体信息的一个工具类。说白一点,就是可以利用它从一个视频文件中提取出视频的相关信息,例如视频轨道,音频轨道,时长,格式等等。 MediaExtractor通常和MediaCodec一起...
  • MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(videoFilePath); 视频文件一般都包含视频、音频等,我们需要获取视频轨道 private static int selectVideoTrack(MediaExtr
  • 最近开始学习Android下的封装和解封装技术,熟悉MediaExtractor和MediaMuxer的使用。 1、MainActivity.java文件: package com.example.tongjiangsong.mediaextractmuxer; import android.support.v7.app....
  • 上一篇讲了如何采集摄像头画面并且进行编码,再进行封装成MP4...Android 使用MediaCodec进行视频编解码工作,这里解码当然还由其来完成,那从MP4文件中提取出H264码流的工作,由MediaExtractor完成 MediaExtrac...
  • 使用MediaExtractor、MediaMuxer去掉视频文件中的音频数据中介绍了 MediaExtractor 类的主要方法,本文主要将使用其 advance() 和seekTo(timeUs, mode) 方法遍历帧,以获取关键帧的时间戳。 advance() 方法能够从...
  • MediaExtractor extractor意思就是提取器,功能就是提取我们所需的流(音频/视频),选取指定的track。 常用Api 功能介绍 setDataSource(String path) 设置数据的来源(文件路径or网络流的...
  • 音视频学习demo,正在升级打怪,加油鸭~ Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音);AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览、...这一章,我们来学习如何使用 MediaExtractor.
  • Android开发 多媒体提取器MediaExtractor详解_将一个视频文件分离视频与音频 前言  此篇博客讲解MediaExtractor将一个视频文件分离视频与音频,如果你对MediaExtractor还没有一个笼统的概念建议先了解我...
  • Android提供了MediaPlayer播放器播放媒体文件,其实MediaPlyer只是对Android Media包下的MediaCodec和MediaExtractor进行了包装,方便使用。但是最好理解下Android媒体文件的解码,编码和渲染流程。使用android....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,742
精华内容 696
关键字:

androidmediaextractor