Android 直播流程2()

打包

視音頻在傳輸過程中需要定義相應的格式,這樣傳輸到對端的時候才能正確地被解析出來。

1、HTTP-FLV

Web 2.0時代,要說什么類型網站最火,自然是以國外的Youtube,國內的優酷、土豆網站了。這類網站提供的視頻內容可謂各有千秋,但它們無一例外的都使用了Flash作為視頻播放載體,支撐這些視頻網站的技術基礎就是——Flash 視頻(FLV) 。FLV 是一種全新的流媒體視頻格式,它利用了網頁上廣泛使用的Flash Player 平臺,將視頻整合到Flash動畫中。也就是說,網站的訪問者只要能看Flash動畫,自然也能看FLV格式視頻,而無需再額外安裝其它視頻插件,FLV視頻的使用給視頻傳播帶來了極大便利。

HTTP-FLV即將音視頻數據封裝成FLV,然后通過HTTP協議傳輸給客戶端。而作為上傳端只需要將FLV格式的視音頻傳輸到服務器端即可。

一般來說FLV格式的視音頻,里面視頻一般使用h264格式,而音頻一般使用AAC-LC格式。

FLV格式是先傳輸FLV頭信息,然后傳輸帶有視音頻參數的元數據(Metadata),然后傳輸視音頻的參數信息,然后傳輸視音頻數據。

2、RTMP

RTMP是Real Time Messaging Protocol(實時消息傳輸協議)的首字母縮寫。該協議基于TCP,是一個協議簇,包括RTMP基本協議及RTMPT/RTMPS/RTMPE等多種變種。RTMP是一種設計用來進行實時數據通信的網絡協議,主要用來在Flash/AIR平臺和支持RTMP協議的流媒體/交互服務器之間進行音視頻和數據通信。

RTMP協議是Adobe公司推出的實時傳輸協議,主要用于基于flv格式的音視頻流的實時傳輸。得到編碼后的視音頻數據后,先要進行FLV包裝,然后封包成rtmp格式,然后進行傳輸。

使用RTMP格式進行傳輸,需要先連接服務器,然后創建流,然后發布流,然后傳輸相應的視音頻數據。整個發送是用消息來定義的,rtmp定義了各種形式的消息,而為了消息能夠很好地發送,又對消息進行了分塊處理,整個協議較為復雜。

差網絡處理

好的網絡下視音頻能夠得到及時的發送,不會造成視音頻數據在本地的堆積,直播效果流暢,延時較小。而在壞的網絡環境下,視音頻數據發送不出去,則需要我們對視音頻數據進行處理。差網絡環境下對視音頻數據一般有四種處理方式:緩存區設計、網絡檢測、丟幀處理、降碼率處理。

1、緩沖區設計

視音頻數據傳入緩沖區,發送者從緩沖區獲取數據進行發送,這樣就形成了一個異步的生產者消費者模式。生產者只需要將采集、編碼后的視音頻數據推送到緩沖區,而消費者則負責從這個緩沖區里面取出數據發送。

2、網絡檢測

差網絡處理過程中一個重要的過程是網絡檢測,當網絡變差的時候能夠快速地檢測出來,然后進行相應的處理,這樣對網絡反應就比較靈敏,效果就會好很多。

我們這邊通過實時計算每秒輸入緩沖區的數據和發送出去數據,如果發送出去的數據小于輸入緩沖區的數據,那么說明網絡帶寬不行,這時候緩沖區的數據會持續增多,這時候就要啟動相應的機制。

3、丟幀處理

當檢測到網絡變差的時候,丟幀是一個很好的應對機制。視頻經過編碼后有關鍵幀和非關鍵幀,關鍵幀也就是一副完整的圖片,而非關鍵幀描述圖像的相對變化。

丟幀策略多鐘多樣,可以自行定義,一個需要注意的地方是:如果要丟棄P幀(非關鍵幀),那么需要丟棄兩個關鍵幀之間的所有非關鍵幀,不然的話會出現馬賽克。對于丟幀策略的設計因需求而異,可以自行進行設計。

4、降碼率

在Android中,如果使用了硬編進行編碼,在差網絡環境下,我們可以實時改變硬編的碼率,從而使直播更為流暢。當檢測到網絡環境較差的時候,在丟幀的同時,我們也可以降低視音頻的碼率。在Android sdk版本大于等于19的時候,可以通過傳遞參數給MediaCodec,從而改變硬編編碼器出來數據的碼率。

(2)封裝

沿用前面的比喻,封裝可以理解為采用哪種貨車去運輸,也就是媒體的容器。 所謂容器,就是把編碼器生成的多媒體內容(視頻,音頻,字幕,章節信息等)混合封裝在一起的標準。容器使得不同多媒體內容同步播放變得很簡單,而容器的另一個作用就是為多媒體內容提供索引,也就是說如果沒有容器存在的話一部影片你只能從一開始看到最后,不能拖動進度條,而且如果你不自己去手動另外載入音頻就沒有聲音。

下面是幾種常見的封裝格式:

1)AVI 格式(后綴為 .avi)

2)DV-AVI 格式(后綴為 .avi)

3)QuickTime File Format 格式(后綴為 .mov)

4)MPEG 格式(文件后綴可以是 .mpg .mpeg .mpe .dat .vob .asf .3gp .mp4等)

5)WMV 格式(后綴為.wmv .asf)

6)Real Video 格式(后綴為 .rm .rmvb)

7)Flash Video 格式(后綴為 .flv)

8)Matroska 格式(后綴為 .mkv)

9)MPEG2-TS 格式 (后綴為 .ts) 目前,我們在流媒體傳輸,尤其是直播中主要采用的就是 FLV 和 MPEG2-TS 格式,分別用于 RTMP/HTTP-FLV 和 HLS 協議。

4.推流到服務器

推流是直播的第一公里,直播的推流對這個直播鏈路影響非常大,如果推流的網絡不穩定,無論我們如何做優化,觀眾的體驗都會很糟糕。所以也是我們排查問題的第一步,如何系統地解決這類問題需要我們對相關理論有基礎的認識。 推送協議主要有三種:

RTSP(Real Time Streaming Protocol):實時流傳送協議,是用來控制聲音或影像的多媒體串流協議, 由Real Networks和Netscape共同提出的; RTMP(Real Time Messaging Protocol):實時消息傳送協議,是Adobe公司為Flash播放器和服務器之間音頻、視頻和數據傳輸 開發的開放協議; HLS(HTTP Live Streaming):是蘋果公司(Apple Inc.)實現的基于HTTP的流媒體傳輸協議; RTMP協議基于 TCP,是一種設計用來進行實時數據通信的網絡協議,主要用來在 flash/AIR 平臺和支持 RTMP 協議的流媒體/交互服務器之間進行音視頻和數據通信。支持該協議的軟件包括 Adobe Media Server/Ultrant Media Server/red5 等。 它有三種變種:

RTMP工作在TCP之上的明文協議,使用端口1935; RTMPT封裝在HTTP請求之中,可穿越防火墻; RTMPS類似RTMPT,但使用的是HTTPS連接; RTMP 是目前主流的流媒體傳輸協議,廣泛用于直播領域,可以說市面上絕大多數的直播產品都采用了這個協議。 RTMP協議就像一個用來裝數據包的容器,這些數據可以是AMF格式的數據,也可以是FLV中的視/音頻數據。一個單一的連接可以通過不同的通道傳輸多路網絡流。這些通道中的包都是按照固定大小的包傳輸的。?

服務器流分發

直播CDN分發網絡

CDN,中文名稱是內容分發網絡,可以用來分發直播、點播、網頁靜態文件、小文件等等,幾乎我們日常用到的互聯網產品都是有CDN在背后提供支持。現在有很多公司在提供云服務,這是在CDN的基礎上,提供了更豐富的一站式接入的云服務能力。例如PP云服務為客戶提供直播、點播、靜態文件、短視頻等多種云服務和CDN加速能力。

概念:負載均衡、CDN緩存、回源、就近原則

在這樣的架構下,會延伸出這樣的幾個概念:

當觀眾人數不太多的時候,例如總共只有1000人,那么是選擇讓某一臺服務器服務這1000人,還是3臺服務器分擔1000人,還是2臺?機器也會有新舊之分,老機器只能抗800數量,那要怎么來分配呢?等等問題。這里就需要有一個策略來做資源的分配。這個策略叫做:負載均衡。

因為觀眾看到的數據都是一樣的,所以呢,數據會在服務器1、2、3上都存儲一份。這個概念叫做:CDN緩存。

當分配到服務器1的第一個觀眾進入時,服務器1是沒有存儲數據的,它會向服務器-0獲取數據,這個過程叫做:回源;相應的,服務器-0被稱為:源站;觀眾請求的數據如果由CDN緩存提供,叫做緩存命中,所有用戶請求的緩存命中比例叫做緩存命中率,它是衡量CDN質量的關鍵指標。

一名新進入的觀眾會被分配到哪一臺服務器上呢?理論上,這臺服務器距離用戶的網絡鏈路越短、不跨網,數據的傳輸的穩定性就越好,這個叫做:就近原則。

跨地區、多運營商覆蓋的CDN

由于就近原則的存在,為了滿足全國甚至全世界不同地方的人,那我們就需要把服務器分布在不同的地區。又由于不同的網絡運營商之間的網絡傳輸會有穩定性問題,那么就需要在不同的網絡運營商里也放置服務器,于是,一個CDN網絡就成型了:

傳統直播一般是基于CDN網絡進行分發,可支持大規模并發(并發數取決于CDN網絡容量)。與傳統CDN的大文件,小文件分發不同,由于直播分布區域分散,一般除了提供播放端的下行分發網絡外,還提供上行主播推流匯聚網絡。只有 一些直播內容資源集中的業務方,會要求直播CDN直接回自己的源站,如電視臺。

上行匯聚

目前傳統直播 CDN 上行一般使用 RTMP 協議,當然也有一些使用 UDP(UDP 方式由于需要 SDK 配合,目前行業內有人在做,但是需要綁定 SDK)。另外國外還有使用 http-ts 的方式進行推流的,可參見 nginx-rtmp 項目大神開源的 nginx-ts-module。當然,目前使用這種方式,關鍵問題還是在于端的支持問題,而該開源項目目前只支持 HLS 和 Dash 的播放。

除了主播推流以外,還有一種方式即從匯聚點到業務方源站去拉流的方式

下行分發

目前下行分發一般使用的協議,rtmp,http-flv,hls 三種協議。這三種協議的優劣,網上已經有很多文章了,一般從終端兼容性,延遲,首屏幾個維度去考慮,這里就不在進行比較。

rtmp 和 http-flv

由于 rtmp 協議在發送數據前交互次數較多,比較追求首屏的直播平臺一般都會選擇 http-flv 協議作為下行分發協議,線上環境測試效果平均會增加 100-200 ms 左右的時間,網絡越差,這個值越大。

rtmp 和 http-flv 的延遲可以做到 3s 以內,但是由于網絡環境的復雜,過低的延遲會導致卡頓率的提升,所以一般 CDN 會用戶接入時,給用戶多發幾秒鐘的數據(一般是 5-8s),填充播放端緩沖區,來抗網絡端的抖動。細節技術會在后面的文章中介紹

hls

hls 對 Android 端和 IOS 端支持較好,并且對 P2P 的支持也較好,一般對延遲要求不高的直播平臺(如體育賽事)會選用這個協議。

hls 的延遲一般和切片大小有關,一般切片是 6-8s 一個片,這個大小對一般主播推流 GOP 適配最好。過高會導致延遲加大,過低,可能切片里就沒有關鍵幀。一般 m3u8 文件里會有 3 個 ts 文件,播放器會在下完兩個片以后,開始播放,并且同時下第三個片。因此一般 hls 的時延在 15s 左右。

當然如果用戶調小 GOP(1s),CDN 端將切片方式配置為按 GOP 切片的方式,HLS 實際也可以做到 5s 以內延遲的。當然壞處就是會導致卡頓率變高

拉流播放器播放

視音頻編解碼一般分為兩種,一種是硬編實現,一種是軟編實現。這兩種方式各有優缺點,硬編性能好,但是需要對兼容性進行相應處理;軟編兼容性好,可以進行一些參數設置,但是軟編一般性能較差,引入相關的編解碼庫往往會增大app的整體體積,而且還需要寫相應的jni接口。

先介紹幾個和視音頻相關的類,通過這幾個類的組合使用,其實是能變換出許多視音頻處理的相關功能

MediaMetadataRetriever::用來獲取視頻的相關信息,例如視頻寬高、時長、旋轉角度、碼率等等。

MediaExtractor::視音頻分離器,將一些格式的視頻分離出視頻軌道和音頻軌道。

MediaCodec:視音頻相應的編解碼類。

MediaMuxer:視音頻合成器,將視頻和音頻合成相應的格式。

MediaFormat:視音頻相應的格式信息。

MediaCodec.BufferInfo:存放ByteBuffer相應信息的類。

MediaCrypto:視音頻加密解密處理的類。

MediaCodecInfo:視音頻編解碼相關信息的類。

MediaFormat和MediaCodec.BufferInfo是串起上面幾個類的橋梁,上面幾個視音頻處理的類通過這兩個橋梁建立起聯系,從而變化出相應的功能

MediaMetadataRetriever

MediaMetadataRetriever用來獲取視音頻的相關信息,MediaMetadataRetriever的使用十分簡單,傳入相應的文件路徑創建MediaMetadataRetriever,之后便可以得到視頻的相關參數。

MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();

metadataRetriever.setDataSource(file.getAbsolutePath());

String widthString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);

if(!TextUtils.isEmpty(widthString)) {

? ? width = Integer.valueOf(widthString);

}

String heightString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);

if(!TextUtils.isEmpty(heightString)) {

? ? height = Integer.valueOf(heightString);

}

String durationString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);

if(!TextUtils.isEmpty(durationString)) {

? ? duration = Long.valueOf(durationString);

}

String bitrateString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);

if(!TextUtils.isEmpty(bitrateString)) {

? ? bitrate = Integer.valueOf(bitrateString);

}

String degreeStr = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);

if (!TextUtils.isEmpty(degreeStr)) {

? ? degree = Integer.valueOf(degreeStr);

}

metadataRetriever.release();

MediaExtractor

MediaExtractor用來對視音頻進行分離,對文件中的視頻音頻軌道進行分離能做大量的事情,比如說要寫一個播放器,那么首先的第一個步驟是分離出視頻音頻軌道,然后進行相應的處理。

MediaExtractor的創建和MediaMetadataRetriever一樣十分簡單,只需要傳入相應的文件路徑。通過getTrackCount()可以得到相應的軌道數量,一般情況下視音頻軌道都有,有些時候可能只有視頻,有些時候可能只有音頻。軌道的序號從0開始,通過getTrackFormat(int index)方法可以得到相應的MediaFormat,而通過MediaFormat可以判斷出軌道是視頻還是音頻。通過selectTrack(int index)方法選擇相應序號的軌道。

public static MediaExtractor createExtractor(String path) throws IOException {

? ? MediaExtractor extractor;

? ? File inputFile = new File(path);? // must be an absolute path

? ? if (!inputFile.canRead()) {

? ? ? ? throw new FileNotFoundException("Unable to read " + inputFile);

? ? }

? ? extractor = new MediaExtractor();

? ? extractor.setDataSource(inputFile.toString());

? ? return extractor;

}

public static String getMimeTypeFor(MediaFormat format) {

? ? return format.getString(MediaFormat.KEY_MIME);

}

public static int getAndSelectVideoTrackIndex(MediaExtractor extractor) {

? ? for (int index = 0; index < extractor.getTrackCount(); ++index) {

? ? ? ? if (isVideoFormat(extractor.getTrackFormat(index))) {

? ? ? ? ? ? extractor.selectTrack(index);

? ? ? ? ? ? return index;

? ? ? ? }

? ? }

? ? return -1;

}

public static int getAndSelectAudioTrackIndex(MediaExtractor extractor) {

? ? for (int index = 0; index < extractor.getTrackCount(); ++index) {

? ? ? ? if (isAudioFormat(extractor.getTrackFormat(index))) {

? ? ? ? ? ? extractor.selectTrack(index);

? ? ? ? ? ? return index;

? ? ? ? }

? ? }

? ? return -1;

}

public static boolean isVideoFormat(MediaFormat format) {

? ? return getMimeTypeFor(format).startsWith("video/");

}

public static boolean isAudioFormat(MediaFormat format) {

? ? return getMimeTypeFor(format).startsWith("audio/");

}

選擇好一個軌道后,便可以通過相應方法提取出相應軌道的數據。extractor.seekTo(startTime, SEEK_TO_PREVIOUS_SYNC)方法可以直接跳轉到開始解析的位置。extractor.readSampleData(byteBuffer, 0)方法則可以將數據解析到byteBuffer中。extractor.advance()方法則將解析位置進行前移,準備下一次解析。

下面是MediaExtractor一般的使用方法。

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;

MediaCodec

MediaCodec是Android視音頻里面最為重要的類,它主要實現的功能是對視音頻進行編解碼處理。在編碼方面,可以對采集的視音頻數據進行編碼處理,這樣的話可以對數據進行壓縮,從而實現以較少的數據量存儲視音頻信息。在解碼方面,可以解碼相應格式的視音頻數據,從而得到原始的可以渲染的數據,從而實現視音頻的播放。

一般場景下音頻使用的是AAC-LC的格式,而視頻使用的是H264格式。這兩種格式在MediaCodec支持的版本(Api 16)也都得到了很好的支持。在直播過程中,先采集視頻和音頻數據,然后將原始的數據塞給編碼器進行硬編,然后得到相應的編碼后的AAC-LC和H264數據。

在Android系統中,MediaCodec支持的格式有限,在使用MediaCodec之前需要對硬編類型的支持進行檢測,如果MediaCodec支持再進行使用。

1、檢查

在使用硬編編碼器之前需要對編碼器支持的格式進行檢查,在Android中可以使用MediaCodecInfo這個類來獲取系統對視音頻硬編的支持情況。

下面的代碼是判斷MediaCodec是否支持某個MIME:

private static MediaCodecInfo selectCodec(String mimeType) {

? ? int numCodecs = MediaCodecList.getCodecCount();

? ? for (int i = 0; i < numCodecs; i++) {

? ? ? ? MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);

? ? ? ? if (!codecInfo.isEncoder()) {

? ? ? ? ? ? continue;

? ? ? ? }

? ? ? ? String[] types = codecInfo.getSupportedTypes();

? ? ? ? for (int j = 0; j < types.length; j++) {

? ? ? ? ? ? if (types[j].equalsIgnoreCase(mimeType)) {

? ? ? ? ? ? ? ? return codecInfo;

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? return null;

}

根據之前的講述,在Android系統中有著不同的顏色格式,有著各種類型的YUV顏色格式和RGB顏色格式。在攝像頭采集的文章中已經講述,需要設置攝像頭采集的圖像顏色格式,一般來說設置為ImageFormat.NV21,之后在攝像頭PreView的回調中得到相應的圖像數據。

在Android系統中不同手機中的編碼器支持著不同的顏色格式,一般情況下并不直接支持NV21的格式,這時候需要將NV21格式轉換成為編碼器支持的顏色格式。在攝像頭采集的文章中已經詳細講述YUV圖像格式和相應的存儲規則,YUV圖像格式的轉換可以使用LibYuv

這里說一下MediaCodec支持的圖像格式。一般來說Android MediaCodec支持如下幾種格式:

/**

? ? * Returns true if this is a color format that this test code understands (i.e. we know how

? ? * to read and generate frames in this format).

? ? */

? ? private static boolean isRecognizedFormat(int colorFormat) {

? ? ? ? switch (colorFormat) {

? ? ? ? ? ? // these are the formats we know how to handle for this test

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:

? ? ? ? ? ? case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? default:

? ? ? ? ? ? ? ? return false;

? ? ? ? }

? ? }

上面大致統計了Android各種手機MediaCodec支持的各種顏色格式,上面5個類型是比較常用的類型。

另外MediaCodec支持Surface的方式輸入和輸出,當編碼的時候只需要在Surface上進行繪制就可以輸入到編碼器,而解碼的時候可以將解碼圖像直接輸出到Surface上,使用起來相當方便,需要在Api 18或以上。

2、創建

當需要使用MediaCodec的時候,首先需要根據視音頻的類型創建相應的MediaCodec。在直播項目中視頻使用了H264,而音頻使用了AAC-LC。在Android中創建直播的音頻編碼器需要傳入相應的MIME,AAC-LC對應的是audio/mp4a-latm,而H264對應的是video/avc。如下的代碼展示了兩個編碼器的創建,其中視頻編碼器的輸入設置成為了Surface的方式。

? ? //Audio

? ? public static MediaCodec getAudioMediaCodec() throws IOException {

? ? ? ? int size = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

? ? ? ? MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 1);

? ? ? ? format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

? ? ? ? format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1000);

? ? ? ? format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);

? ? ? ? format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, size);

? ? ? ? format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);

? ? ? ? MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");

? ? ? ? mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

? ? ? ? return mediaCodec;

? ? }

? ? //Video

? ? public static MediaCodec getVideoMediaCodec() throws IOException {

? ? ? ? int videoWidth = getVideoSize(1280);

? ? ? ? int videoHeight = getVideoSize(720);

? ? ? ? MediaFormat format = MediaFormat.createVideoFormat("video/avc", videoWidth, videoHeight);

? ? ? ? format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

? ? ? ? format.setInteger(MediaFormat.KEY_BIT_RATE, 1300* 1000);

? ? ? ? format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);

? ? ? ? format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

? ? ? ? format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);

? ? ? ? format.setInteger(MediaFormat.KEY_COMPLEXITY,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);

? ? ? ? MediaCodec mediaCodec = MediaCodec.createEncoderByType("video/avc");

? ? ? ? mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

? ? ? ? return mediaCodec;

? ? }

? ? // We avoid the device-specific limitations on width and height by using values that

? ? // are multiples of 16, which all tested devices seem to be able to handle.

? ? public static int getVideoSize(int size) {

? ? ? ? int multiple = (int) Math.ceil(size/16.0);

? ? ? ? return multiple*16;

? ? }

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容