MediaCodec

簡(jiǎn)介

MediaCodec是 Android media 基礎(chǔ)框架的一部分,通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, SurfaceAudioTrack 一起使用。

MediaCodec_1.png

這張圖描述了MediaCodec的整體工作流程,一般情況下,由客戶端向MediaCodec申請(qǐng)一個(gè)空的輸入ByteBuffer,進(jìn)行數(shù)據(jù)填充,再將ByteBuffer發(fā)送給MediaCodec,MediaCodec會(huì)采用異步的方式處理這些輸入的數(shù)據(jù),并將處理后的數(shù)據(jù)填充到輸出Buffer中,消費(fèi)者取到輸出Buffer進(jìn)行消費(fèi)后,需將緩沖區(qū)釋放,返還給MediaCodec。

數(shù)據(jù)

數(shù)據(jù)載體模式:

  • ByteBuffer模式:3種數(shù)據(jù)類(lèi)型都可使用ByteBuffer模式。這種模式下,通??梢允褂肐mage類(lèi)和getInput/OutputImage(int)獲取原始視頻幀,下面會(huì)具體列舉到。

  • Surface模式:當(dāng)處理原始視頻數(shù)據(jù)時(shí),應(yīng)該考慮使用Surface,而不是ByteBuffer來(lái)作為數(shù)據(jù)載體,這樣可以提高編解碼器性能。因?yàn)?,Surface使用的是更底層的視頻緩沖區(qū),而不是將數(shù)據(jù)映射或復(fù)制到ByteBuffer中,效率更高。這種模式下,可以使用ImageReader類(lèi)來(lái)訪問(wèn)原始視頻幀,并且它仍然比ByteBuffer模式高效。

數(shù)據(jù)類(lèi)型:

1)壓縮數(shù)據(jù):作為解碼器的輸入數(shù)據(jù)或者編碼器的輸出數(shù)據(jù)

  • 指定格式:通過(guò)MediaFormat.KEY_MIME來(lái)指定或獲取格式,編解碼器才知道如何處理這些數(shù)據(jù)。
  • 視頻數(shù)據(jù):一般情況下,輸入給解碼器或從編碼器得到的一個(gè)ByteBuffer,都會(huì)是完整的一幀數(shù)據(jù)。除非設(shè)置了BUFFER_FLAG_PARTIAL_FRAME標(biāo)記,它表示了緩沖區(qū)只包含幀的一部分,解碼器會(huì)對(duì)數(shù)據(jù)進(jìn)行批處理,直到?jīng)]有該標(biāo)志的緩沖區(qū)出現(xiàn),才開(kāi)始解碼。比如H264解碼,必須將分割符和NALU單元作為一個(gè)完整的數(shù)據(jù)幀,傳給解碼器才能正確解碼。
  • 音頻數(shù)據(jù):音頻的要求則要稍微寬松,一個(gè)ByteBuffer可能包含多個(gè)編碼的音頻訪問(wèn)單元。

2) 原始音頻數(shù)據(jù):ByteBuffer包含PCM音頻數(shù)據(jù)的整個(gè)幀,這是每個(gè)聲道按聲道順序的一個(gè)樣本,每個(gè)PCM音頻樣本都是16位帶符號(hào)整數(shù)或浮點(diǎn)數(shù)。格式為AudioFormat.ENCODING_PCM_16BIT才能做處理。

  • 解碼器輸出:通過(guò)getOutputFormat()來(lái)獲取MediaFormat
  • 編碼器輸入:通過(guò)getInputFormat()來(lái)獲取MediaFormat

獲取音頻采樣數(shù)據(jù)的示例代碼如下:

/**
  * 根據(jù)聲道獲取采樣數(shù)據(jù)
  */
 short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
  //獲取輸出緩沖區(qū)
  ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
  //獲取音頻編碼格式
  MediaFormat format = codec.getOutputFormat(bufferId);
  //轉(zhuǎn)換字節(jié)順序和Short類(lèi)型
  ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
  //獲取聲道數(shù)
  int numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
  if (channelIx < 0 || channelIx >= numChannels) {
    return null;
  }
  //獲取存儲(chǔ)采樣數(shù)據(jù)
  short[] res = new short[samples.remaining() / numChannels];
  for (int i = 0; i < res.length; ++i) {
    res[i] = samples.get(i * numChannels + channelIx);
  }
  return res;
 }

3) 原始視頻數(shù)據(jù):在ByteBuffer模式下,視頻緩沖區(qū)根據(jù)MediaFormat.KEY_COLOR_FORMAT來(lái)進(jìn)行布局。通過(guò)getCodecInfo().getCapabilitiesForType().colorFormats可以獲取到支持的顏色格式數(shù)組,它包含3種顏色格式:

  • native raw video format: KEY_COLOR_FORMAT為COLOR_FormatSurface,表示該數(shù)據(jù)將是GraphicBuffer元數(shù)據(jù)的引用,在OMX中(即軟編解碼)中,被稱(chēng)為OMX_COLOR_FormatAndroidOpaque,這類(lèi)格式的數(shù)據(jù)可以作為Surface模式的輸入和輸出。

  • flexible YUV buffers:比如CodecCapabilities.COLOR_FormatYUV420Flexible,Surface模式和ByteBuffer模式2中模式,都可以作為輸入和輸出。

  • other, specific formats: 除了以上2種,只支持ByteBuffer模式,在MediaCodecInfo.CodecCapabilities中可以看到許多不同的格式,只要后綴為flexible類(lèi)型的,如,CodecCapabilities.COLOR_FormatRGBFlexible, 都可以使用Image類(lèi)的getInput/OutputImage(int)來(lái)獲取原始視頻幀。

Build.VERSION_CODES.LOLLIPOP_MR1(5.1)以后,編解碼器都支持YUV420P

生命周期和狀態(tài)

MediaCodec有3種狀態(tài):Stopped,Executing和Released,其中Stopped和Released又各自細(xì)分成3種子狀態(tài),如下圖所示:


MediaCodec_4.png

1)Stopped

  • Uninitialized:當(dāng)通過(guò)工廠方法了成功創(chuàng)建編解碼器后,此時(shí)處于Uninitialized子狀態(tài)
  • Configured:通過(guò)configure方法配置編解碼器,此時(shí)處于Configured子狀態(tài),接著,需要調(diào)用start方法來(lái)啟動(dòng),讓編解碼器進(jìn)入Executing狀態(tài)的Flushed子狀態(tài),才能輸入數(shù)據(jù)給編解碼器處理
  • Error

2)Executing

  • Flushed:執(zhí)行start方法后,此時(shí)處于Flushed子狀態(tài)
  • Running :當(dāng)?shù)谝粋€(gè)輸入緩沖區(qū)被出隊(duì),編解碼器便進(jìn)入Running子狀態(tài),這意味著,大部分時(shí)間編解碼器都處于此狀態(tài)
  • End-of-Stream:當(dāng)給編解碼器發(fā)送一個(gè)帶有End-of-Stream標(biāo)記的Buffer后,編解碼器就切換為End-of-Stream子狀態(tài),此時(shí),編解碼器不再接收輸入數(shù)據(jù),但仍舊會(huì)繼續(xù)輸出,直到end-of-stream標(biāo)記輸出

3)Released

  • Released:Stopped和Executing都可切換至此狀態(tài),當(dāng)使用編碼器操作完成后,應(yīng)該調(diào)用release方法,使編解碼器進(jìn)入此狀態(tài)

狀態(tài)重置:

  • flush:在Executing狀態(tài)下,調(diào)用flush方法,來(lái)使編解碼器回到Flushed子狀態(tài)
  • stop:在Executing狀態(tài)下,調(diào)用stop方法,使編解碼器進(jìn)入U(xiǎn)ninitialized子狀態(tài),此時(shí)可以調(diào)用configure方法來(lái)重新配置,進(jìn)入下一輪循環(huán)
  • reset:在某些情況下,編解碼器會(huì)出現(xiàn)異常,此時(shí)應(yīng)該使用reset而不是stop方法,使編解碼進(jìn)入U(xiǎn)ninitialized狀態(tài),事實(shí)上,reset可以在任何時(shí)候被調(diào)用,如果異常發(fā)生后,不準(zhǔn)備重新使用編解碼器,那應(yīng)該調(diào)用release進(jìn)行釋放

創(chuàng)建

通過(guò)編解碼器類(lèi)型和編解碼器名字2種方式,可以創(chuàng)建編解碼器,它們分別對(duì)應(yīng)以下工廠方法:

//根據(jù)編碼器類(lèi)型創(chuàng)建解碼器
public static MediaCodec createEncoderByType(@NonNull String type)
//根據(jù)解碼器類(lèi)型創(chuàng)建解碼器
public static MediaCodec createDecoderByType(@NonNull String type)
//根據(jù)編解碼器名字創(chuàng)建編解碼器
public static MediaCodec createByCodecName(@NonNull String name)

MediaFormat包含了許多類(lèi)型,如下:

public final class MediaFormat {
      。。。。。
    //視頻類(lèi)型
    public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
    public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
    public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
    public static final String MIMETYPE_VIDEO_AVC = "video/avc";
    public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
  
    //音頻類(lèi)型
    public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
    public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
    public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
    public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
    public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
    public static final String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
    public static final String MIMETYPE_AUDIO_OPUS = "audio/opus";
      。。。。。
}

在創(chuàng)建解碼器時(shí),如果是本地文件或者網(wǎng)絡(luò)流,可以配合使用MediaExtractor解封裝器來(lái)做格式提取,如下:

//mTrackIndex為視頻軌索引獲取格式
MediaFormat decoderFormat = mediaExtractor.getTrackFormat(mTrackIndex);
//創(chuàng)建解碼器
MediaCodec decoder = MediaCodec.createDecoderByType(decoderFormat.getString(MediaFormat.KEY_MIME));

在創(chuàng)建編碼器時(shí),可以直接指定期望的編碼類(lèi)型,如下:

//指定格式
MediaFormat encoderFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
//創(chuàng)建編碼器
MediaCodec encoder =  MediaCodec.createEncoderByType(encoderFormat.getString(MediaFormat.KEY_MIME));

以上是硬編解碼器的創(chuàng)建,如果創(chuàng)建失敗,可以通過(guò)指定軟解碼器名字來(lái)創(chuàng)建,如下:

//創(chuàng)建軟解碼器
MediaCodec decoder = MediaCodec.createByCodecName("OMX.google.h264.decoder");
//創(chuàng)建軟編碼器
MediaCodec encoder = MediaCodec.createByCodecName("OMX.google.h264.encoder");

通常,以"OMX.google."為前綴的名字,即為軟編解碼類(lèi)型

初始化配置

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

void configure (MediaFormat format, Surface surface, int flags, MediaDescrambler descrambler)
  • MediaCrypto和MediaDescrambler:用于加解密處理,都是可空參數(shù),如果不涉及加密和解密,它們沒(méi)有區(qū)別,會(huì)執(zhí)行同一個(gè)重載方法

  • flags:傳入MediaCodec.CONFIGURE_FLAG_ENCODE時(shí)(它的值為1),用來(lái)指定創(chuàng)建編碼器,奇怪的是MediaCodec沒(méi)有定義一個(gè)類(lèi)似MediaCodec.CONFIGURE_FLAG_DECODE的常量用于創(chuàng)建解碼器,這里通常直接傳入0來(lái)指定創(chuàng)建解碼器

  • Surface:調(diào)用releaseOutputBuffer(int index, boolean render)方法將render設(shè)置為true,解碼器會(huì)將輸出數(shù)據(jù)渲染到此處指定的Surface,當(dāng)Surface不再使用或顯示時(shí),緩沖區(qū)會(huì)自動(dòng)釋放給編解碼器

以下為編解碼配置的示例代碼:

//解碼器配置
decoder.configure(decoderFormat, new Surface(new SurfaceTexture(getTextureId())), null, 0);
decoder.start()

這里指定了解碼器最終輸出渲染的表面,用來(lái)輸出到開(kāi)辟好的紋理空間

//編碼器配置
encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = encoder.createInputSurface()
encoder.start()

編碼器創(chuàng)建的Surface只能用于硬件加速api的渲染輸入,如OpenGL,這就是Surface模式的用法,比如編碼器創(chuàng)建了自己的Surface,可以用來(lái)關(guān)聯(lián)EGL做為編碼器的數(shù)據(jù)輸入,替代ByteBuffer輸入方式。
還有另一種方式,也可以使用createPersistentInputSurface ()來(lái)創(chuàng)建Surface,其他的編碼器隨后可以調(diào)用setInputSurface(Surface)方法來(lái)繼續(xù)使用這個(gè)Surface,但同一時(shí)間內(nèi),只能一個(gè)編解碼器使用。

createInputSurface和setInputSurface方法必須在configure方法之后,start方法之前調(diào)用,否則拋IllegalStateException異常。
持久化表面必須使用createPersistentInputSurface創(chuàng)建,否則拋IllegalArgumentException異常。

特殊數(shù)據(jù)

某些格式如,AAC和MPEG4,H.264和H.265要求幀數(shù)據(jù)的前綴,包含設(shè)置數(shù)據(jù)或編解碼器特定數(shù)據(jù)的緩沖區(qū),如sps和pps。處理此類(lèi)壓縮格式時(shí),必須在start方法之后且任何幀數(shù)據(jù)之前,將這些數(shù)據(jù)輸送給編解碼器。這類(lèi)數(shù)據(jù)用BUFFER_FLAG_CODEC_CONFIG來(lái)做標(biāo)記。

通常不使用ByteBuffer來(lái)直接提交,而是在configure方法時(shí)通過(guò)MediaFormat進(jìn)行設(shè)置,它在start方法調(diào)用后,會(huì)直接提交給編解碼器。編解碼器同樣會(huì)輸出到輸出緩沖區(qū)中,因此,在進(jìn)行編碼往Muxer寫(xiě)入數(shù)據(jù)時(shí),攜帶BUFFER_FLAG_CODEC_CONFIG標(biāo)記的數(shù)據(jù)不用再次寫(xiě)入,它應(yīng)該通過(guò)MediaFormat傳遞給Muxer。

數(shù)據(jù)處理

1)同步方式:

系統(tǒng)版本5.0之前只能使用同步方式來(lái)處理,整個(gè)過(guò)程如開(kāi)頭的流程圖所示,下面以解碼流程為例:

  /**
   * 解封裝
   */
 private int drainExtractor(long timeoutUs) {
        if (mIsExtractorEOS) return DRAIN_STATE_NONE;
        int trackIndex = mExtractor.getSampleTrackIndex();
        if (trackIndex >= 0 && trackIndex != mTrackIndex) {
            return DRAIN_STATE_NONE;
        }
        //步驟1
        int result = mDecoder.dequeueInputBuffer(timeoutUs);
        if (result < 0) return DRAIN_STATE_NONE;
        if (trackIndex < 0) {
            mIsExtractorEOS = true;
            mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            return DRAIN_STATE_NONE;
        }
        //步驟2
        ByteBuffer buffer = mDecoder.getInputBuffer(result);
        int sampleSize = mExtractor.readSampleData(buffer, 0);
        boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
        //步驟3
        mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0);
        mExtractor.advance();

        return DRAIN_STATE_CONSUMED;
}

步驟1:從解碼器獲取輸入緩沖區(qū)的id

  • timeoutUs == 0,立即返回
  • timeoutUs < 0,無(wú)限期等待
  • timeoutUs > 0,等待“timeoutUs”微秒超時(shí)

步驟2:根據(jù)緩沖id獲取輸入緩沖區(qū)

步驟3:將充滿數(shù)據(jù)的輸入緩沖求傳遞給解碼器,最后的flags標(biāo)記有幾種類(lèi)型

  • BUFFER_FLAG_KEY_FRAME:是否關(guān)鍵幀
  • BUFFER_FLAG_CODEC_CONFIG:是否sps和pps等特殊數(shù)據(jù),通常應(yīng)該用MediaFormat做傳遞
  • BUFFER_FLAG_PARTIAL_FRAME:通常一個(gè)ByteBuffer包含一幀完整視頻數(shù)據(jù),除非指定該標(biāo)志,出現(xiàn)該標(biāo)記解碼器會(huì)批量處理多個(gè)緩沖區(qū),直到?jīng)]有該標(biāo)記出現(xiàn),才進(jìn)行解碼,大多數(shù)情況下不會(huì)使用
  • BUFFER_FLAG_END_OF_STREAM:結(jié)束輸入數(shù)據(jù)給解碼器,除非調(diào)用flush方法,否則不要再向編解碼器輸入緩沖區(qū),可以在最后一個(gè)帶有有效數(shù)據(jù)的緩沖區(qū)上加上此標(biāo)記,也可以用一個(gè)空的緩沖區(qū)
    來(lái)傳遞,此時(shí)的pts可為0

下面從解碼器獲取輸出數(shù)據(jù):

private int drainDecoder(long timeoutUs) throws InterruptedException {
        if (mIsDecoderEOS) return DRAIN_STATE_NONE;
        //1
        int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeoutUs);
        switch (result) {
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                return DRAIN_STATE_NONE;
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
        }
        //2
        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            mEncoder.signalEndOfInputStream();
            mIsDecoderEOS = true;
            mBufferInfo.size = 0;
        }
        //3
        boolean doRender = (mBufferInfo.size > 0);
        mDecoder.releaseOutputBuffer(result, doRender);
        ......
        return DRAIN_STATE_CONSUMED;
}

步驟1:從解碼器獲取輸出緩沖區(qū)和緩沖區(qū)信息,result返回緩沖區(qū)索引或常量值

  • INFO_TRY_AGAIN_LATER:獲取超時(shí)
  • INFO_OUTPUT_BUFFERS_CHANGED:表明輸出緩沖區(qū)數(shù)據(jù)已更改,在5.0前,此時(shí)用getOutputBuffers方法獲取緩沖區(qū)數(shù)組,5.0后,此方式已過(guò)期,可以忽略
  • INFO_OUTPUT_FORMAT_CHANGED:表明輸出格式已更改,后續(xù)數(shù)據(jù)將采用新的格式,此時(shí)可以通過(guò)getOutputFormat方法來(lái)獲取新的格式,隨后如果不出現(xiàn)超時(shí),通常開(kāi)始返回正確的輸出緩沖區(qū)索引

步驟2:接受到帶有BUFFER_FLAG_END_OF_STREAM標(biāo)記的緩沖區(qū),表明所有解碼數(shù)據(jù)已輸出完成,不會(huì)再有輸出,signalEndOfInputStream方法通知編碼器輸入結(jié)束

步驟3:使用完輸出緩沖區(qū)后,調(diào)用releaseOutputBuffer釋放回給解碼器,doRender為true,則緩沖區(qū)的數(shù)據(jù)會(huì)渲染到在configure方法中配置的Surface

可以不立即queueinputbuffer/releaseOutputBuffer到編解碼器,但持有input/outputbuffer可能會(huì)使編解碼器停止工作,并且此行為取決于設(shè)備。 編解碼器有可能在產(chǎn)生輸出緩沖區(qū)之前暫停,直到所有未完成的緩沖區(qū)queueinputbuffer/releaseOutputBuffer。 因此,用戶最好每次獲得緩沖區(qū)后執(zhí)行釋放操作。

2)異步方式:

在系統(tǒng)版本5.0及以上增加了異步處理方式,同步方式仍可以使用,但官方推薦首選異步方式,它的流程狀態(tài)和同步方式稍微有些不同,再調(diào)用start方法后,狀態(tài)自動(dòng)切換為Running子狀態(tài),并且在調(diào)用flush方法后,必須再次調(diào)用start使?fàn)顟B(tài)流轉(zhuǎn)為Running狀態(tài),才能開(kāi)始輸入數(shù)據(jù)。


image.png

異步方式的代碼示例如下:

 MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

setCallback方法必須在configure方法之前被調(diào)用,并且不應(yīng)該再使用getInputBuffers,getOutputBuffers,dequeueInputBuffer和dequeueOutputBuffer方法。

最后

當(dāng)編解碼器使用Surface模式作為數(shù)據(jù)輸入源或輸出源時(shí),對(duì)應(yīng)的緩沖區(qū)無(wú)法訪問(wèn):

  • 輸出源:使用輸出Surface時(shí),數(shù)據(jù)處理幾乎與ByteBuffer模式相同。但是,輸出緩沖區(qū)將不可訪問(wèn),并表示為空值。例如,getOutputBuffer / Image(int)將返回null,getOutputBuffers將返回僅包含null的數(shù)組。

  • 輸入源:使用輸入Surface時(shí),沒(méi)有可訪問(wèn)的輸入緩沖區(qū),因?yàn)榫彌_區(qū)會(huì)自動(dòng)從輸入表面?zhèn)鬟f到編解碼器。調(diào)用dequeueInputBuffer會(huì)拋出IllegalStateException,并且getInputBuffers返回一個(gè)不可寫(xiě)入的偽造ByteBuffer數(shù)組。調(diào)用signalEndOfInputStream以信號(hào)流結(jié)束后,輸入表面將立即停止向編解碼器提交數(shù)據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 MediaCodec前面已經(jīng)做了簡(jiǎn)介,那么這一篇就是使用了。 參考文章 官方MediaCodec Andro...
    yzzCool閱讀 2,415評(píng)論 0 2
  • 本篇文章是對(duì)官方的文檔MediaCodec[https://developer.android.google.cn...
    leilifengxingmw閱讀 2,101評(píng)論 0 1
  • 前言 MediaCodec大坑絕對(duì)是大坑,坑的很直溜。本系列是參考 [奇卓社]的文章,喜歡的小伙伴可以直接去看[奇...
    yzzCool閱讀 4,252評(píng)論 0 3
  • 簡(jiǎn)介 從 API 16開(kāi)始,Android提供了MediaCodec類(lèi)以便開(kāi)發(fā)者更加靈活的處理音視頻的編解碼,較M...
    極客匠閱讀 3,278評(píng)論 0 1
  • 推薦指數(shù): 6.0 書(shū)籍主旨關(guān)鍵詞:特權(quán)、焦點(diǎn)、注意力、語(yǔ)言聯(lián)想、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析,社會(huì)...
    Jenaral閱讀 6,037評(píng)論 0 5

友情鏈接更多精彩內(nèi)容