FFmpeg

FFmpeg是一套可以用來記錄、處理數字音頻、視頻,並將其轉換為流的開源框架,採用LPL或GPL許可證,提供了錄製、轉換以及流化音視頻的完整解決方案

1、FFmpeg編譯選項詳解

FFmpeg和大部分的GNU軟件的編譯方式類似,都是通過configure腳本來實現編譯

前定製的,這種方式允許用戶在編譯前對軟件進行裁剪,同時通過對最終運行到的

系統以及目標平臺的配置來決定對某些模塊設定合適的配置。

標準選項:GNU軟件配置項目,例如安裝路徑、--prefix=…等。

編譯、鏈接選項:默認配置是生成靜態庫而不是生成動態庫,例如--disable-static、--enable-shared等。

可執行程序控制選項:決定是否生成FFmpeg、ffplay、ffprobe和ffserver等。

模塊控制選項:裁剪編譯模塊,包括整個庫的裁剪,例如--disable-avdevice;
一組模塊的篩選,例如--disable-decoders;單個模塊的裁剪,例如--disable-demuxer。

能力展示選項[圖片上傳中...(屏幕快照 2019-09-05 上午9.21.53.png-7df104-1567646545418-0)]
:列出當前源代碼支持的各種能力集,例如--list-decoders、--list-encoders。

其他:允許開發者深度定製,如交叉編譯環境配置、自定義編譯

音視頻開發 - FFmpeg

默認的編譯會生成4個可執行文件和8個靜態庫。

可執行文件:

1、ffmpeg:轉碼、推流、媒體文件
2、ffplay:播放媒體文件,需要libSDL的預先編譯
3、ffprobe: 獲取文件詳細信息
4、ffserver:作為簡單流媒體服務器

8個靜態庫:

AVUtil:基礎的模塊之一,基本的音視頻處理操作。

AVFormat:文件格式(avi、MP4、flv、mov等)和協議庫,封裝了Protocol層和Demuxer、Muxer層。

AVCodec:編解碼庫,例如MPEG-4。可以將其他的第三方的Codec以插件的方式添加進來,libx264、FDK-AAC、lame等庫。

AVFilter:音視頻濾鏡庫,包括音頻特效和視頻特效的處理。

AVDevice:輸入輸出設備庫,比如,需要編譯出播放聲音或者視頻的工具ffplay,需要該類。

SwrRessample:該模塊可用於音頻重採樣,可以對數字音頻進行聲道數、數據格式、採樣率等多種基本信息的轉換。

SWScale:該模塊是將圖像進行格式轉換的模塊,比如,可以將YUV的數據轉換為RGB的數據。

PostProc:該模塊可用於進行後期處理,當我們使用AVFilter的時候需要打開該模塊的開關,因為Filter中會使用到該模塊的一些基礎函數。

2、FFmpeg安裝(安裝命令行工具)

  • ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)" 安裝homebrew
  • brew install ffmpeg 安裝ffmpeg

常用的命令行工具:

ffmpeg是進行媒體文件轉碼的命令行工具,ffprobe是用於查看媒體文件頭信息的工具,ffplay則是用於播放媒體文件的工具。

1、ffprobe 查看流媒體格式

  • 首先用ffprobe查看一個音頻的文件:
    ffprobe ~/Desktop/output.mp3 Duration: 00:00:09.44, bitrate: 77 kb/s
    表明該音頻文件的時長是09秒44毫秒,開始播放時間是0,整個媒體文件的比特率是77Kbit/s Stream #0:0: Audio: aac (LC), 44100 Hz, mono, fltp, 77 kb/s
    第一個流是音頻流,編碼格式是aac格式,採樣率是44.1kHz,聲道是mono,採樣表示格式是fltp),這路流的比特率是77Kbit/s。 ...有的視頻中是有多路音頻文件的,比如國語或者是粵語、英語等
  • 然後再使用ffprobe查看一個視頻的文件:
    ffprobe ~/Desktop/aroey.mp4 Metadata:
    major_brand : isom
    minor_version : 512
    compatible_brands: isomiso2avc1mp41
    encoder : Lavf58.20.100
    這行信息表明了該文件的Metadata信息,比如encoder是Lavf55.12.100,其中Lavf代表的是FFmpeg輸出的文件,後面的編號代表了FFmpeg的版本代號,接下來的一行信息如下: Duration: 00:00:09.94, start: 0.000000, bitrate: 5166 kb/s
    Duration是0分09秒94毫秒,開始播放的時間是從0ms開始播放的,整個文件的比特率是5166Kbit/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1080x1920, 5100 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
    這行信息表示第一個stream是視頻流,編碼方式是H264的格式(封裝格式是AVC1),每一幀的數據表示是YUV420P的格式,分辨率是1080x1920,這路流的比特率是5100Kbit/s,幀率是每秒鐘29.97幀(fps是29.97)
  • 其他音視頻文件信息

2、ffplay

ffplay是以FFmpeg框架為基礎,外加渲染音視頻的庫libSDL來構建的媒體文件播放器。

  • ffplay 32037.mp3
    播放音頻
  • ffplay 32037.mp4
    播放視頻,業內的ijkplayer播放器就是基於ffplay開發的
  • 其他播放相關的信息

3、ffmpeg

ffmpeg強大的媒體文件轉換工具。它可以轉換任何格式的媒體文件,並且還可以用自己的AudioFilter以及VideoFilter進行處理和編輯。

  • 通用參數
    -f fmt:指定格式(音頻或者視頻格式)。
    -i filename:指定輸入文件名,在Linux下當然也能指定:0.0(屏幕錄製)或攝像頭。
    -y:覆蓋已有文件。
    -t duration:指定時長。
    -fs limit_size:設置文件大小的上限。
    -ss time_off:從指定的時間(單位為秒)開始,也支持[-]hh:mm:ss[.xxx]的格式。
    -re:代表按照幀率發送,尤其在作為推流工具的時候一定要加入該參數,否則ffmpeg會按照最高速率向流媒體服務器不停地發送數據。
    -map:指定輸出文件的流映射關係。例如:“-map 1:0-map 1:1”要求將第二個輸入文件的第一個流和第二個流寫入輸出文件。如果沒有-map選項,則ffmpeg採用默認的映射關係。
  • 視頻參數

    -b:指定比特率(bit/s),ffmpeg是自動使用VBR的,若指定了該參數則使用平均比特率。
    -bitexact:使用標準比特率。
    -vb:指定視頻比特率(bits/s)。
    -r rate:幀速率(fps)。
    -s size:指定分辨率(320×240)。
    -aspect aspect:設置視頻長寬比(4:3,16:9或1.3333,1.7777)。
    -croptop size:設置頂部切除尺寸(in pixels)。
    -cropbottom size:設置底部切除尺寸(in pixels)。
    -cropleft size:設置左切除尺寸(in pixels)。
    -cropright size:設置右切除尺寸(in pixels)。
    -padtop size:設置頂部補齊尺寸(in pixels)。
    -padbottom size:底補齊(in pixels)。
    -padleft size:左補齊(in pixels)。
    -padright size:右補齊(in pixels)。
    -padcolor color:補齊帶顏色(000000-FFFFFF)。
    -vn:取消視頻的輸出。
    -vcodec codec:強制使用codec編解碼方式('copy'代表不進行重新編碼)。
  • 音頻參數
    -ab:設置比特率(單位為bit/s,老版的單位可能是Kbit/s),對於MP3格式,若要聽到較高品質的聲音則建議設置為160Kbit/s(單聲道則設置為80Kbit/s)以上。
    -aq quality:設置音頻質量(指定編碼)。
    -ar rate:設置音頻採樣率(單位為Hz)。

    -ac channels:設置聲道數,1就是單聲道,2就是立體聲。
    -an:取消音頻軌。
    -acodec codec:指定音頻編碼('copy'代表不做音頻轉碼,直接複製)。
    -vol volume:設置錄製音量大小(默認為256)。

簡單使用
轉化格式: ffmpeg -i 目標地址.webm 最終.mp4
分離視頻: ffmpeg -i 目標地址.mp4 -vcodec copy -an 最終.mp4
分離音頻: ffmpeg -i 目標地址.mp4 -acodec copy -vn 最終.aac

3、FFmpeg交叉編譯生成IOS/Android端的代碼API

  • 下載編譯FFmpeg所需要的腳本文件gas-preprocessor.pl 下載地址: https://github.com/libav/gas-preprocessor 複製gas-preprocessor.pl到/usr/sbin下,(這個應該是複製到/usr/local/bin) 修改文件權限:chmod 777 /usr/local/bin/gas-preprocessor.pl
  • 下載腳本FFmpeg腳本 地址: https://github.com/kewlbear/FFmpeg-iOS-build-script 解壓,找到文件 build-ffmpeg.sh 執行服本文件:./build-ffmpeg.sh

介紹幾個術語:

容器/文件(Conainer/File):即特定格式的多媒體文件,比如MP4、flv、mov等。

媒體流(Stream):表示時間軸上的一段連續數據,如一段聲音數據、一段視頻數據或一段字幕數據,可以是壓縮的,也可以是非壓縮的,壓縮的數據需要關聯特定的編解碼器。

數據幀/數據包(Frame/Packet):通常,一個媒體流是由大量的數據幀組成的,對於壓縮數據,幀對應著編解碼器的最小處理單元,分屬於不同媒體流的數據幀交錯存儲於容器之中。

編解碼器:編解碼器是以幀為單位實現壓縮數據和原始數據之間的相互轉換的。

其中AVFormatContext就是對容器或者說媒體文件層次的一個抽象,該文件中(或者說在這個容器裡面)包含了多路流(音頻流、視頻流、字幕流等),對流的抽象就是AVStream;在每一路流中都會描述這路流的編碼格式,對編解碼格式以及編解碼器的抽象就是AVCodecContext與AVCodec;對於編碼器或者解碼器的輸入輸出部分,也就是壓縮數據以及原始數據的抽象就是AVPacket與AVFrame;除了編解碼之外,對於音視頻的處理肯定是針對於原始數據的處理,也就是針對於AVFrame的處理,使用的就是AVFilter。

案例1:將視頻文件解碼成單獨的PCM文件和YUV文件

//1.註冊協議、格式與編解碼器
//註冊網絡協議部分
avformat_network_init();

//註冊所有的編解碼器、協議、格式
av_register_all();




//2.打開媒體文件源,並設置超時回調(本地磁盤文件也可能是網絡媒體資源鏈接,會涉及RTMP/HTTP等協議的視頻源,設置超時時間)
AVFormatContext *formatCtx = avformat_alloc_context();
AVIOInterruptCB int_cb = {interrupt_callback,(__bridge void *)(self)};
formatCtx->interrupt_callback = int_cb;
avformat_open_input(formatCtx, path, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);


//3.尋找各個流,並且打開對應的解碼器
for (int i = 0; i<formatctx->nb_streams; i++) {
AVStream *stream = formatCtx->streams[i];
if (AVMEDIA_TYPE_VIDEO == stream->codec->codec_type) {
//視頻流
videoStreamIndex = i;
videoCodecCtx = stream->codec;
}else if(AVMEDIA_TYPE_AUDIO == stream->codec->codec_type){
//音頻流
audioStreamIndex = i;
audioCodecCtx = stream->codec;
}
}

//打開音頻解碼器
AVCodec *codec = avcodec_find_decoder(audioCodecCtx ->codec_id);
if(!codec){
// 找不到對應的音頻解碼器

}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0){
// 打開音頻解碼器失敗

}

//打開視頻流解碼器
AVCodec *codec1 = avcodec_find_decoder(videoCodecCtx->codec_id);
if(!codec) {

// 找不到對應的視頻解碼器

}
int openCodecErrCode1 = 0;
if ((openCodecErrCode1 = avcodec_open2(codecCtx, codec1, NULL)) < 0) {
// 打開視頻解碼器失敗
}


//4.初始化解碼後數據的結構體
//4.1需要分配出解碼之後的數據所存放的內存空間
SwrContext *swrContext = NULL;
if(audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
// 如果不是我們需要的數據格式
swrContext = swr_alloc_set_opts(NULL,outputChannel, AV_SAMPLE_FMT_S16, outSampleRate,in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
if(!swrContext || swr_init(swrContext)) {
if(swrContext) {
swr_free(&swrContext);

}

}
audioFrame = avcodec_alloc_frame();
}
//4.2以及進行格式轉換所需要用到的對象
AVPicture picture;
bool pictureValid = avpicture_alloc(&picture,PIX_FMT_YUV420P,videoCodecCtx->width,videoCodecCtx->height) == 0;
if (!pictureValid){
// 分配失敗
return false;

}
swsContext = sws_getCachedContext(swsContext,
videoCodecCtx->width,
videoCodecCtx->height,
videoCodecCtx->pix_fmt,
videoCodecCtx->width,
videoCodecCtx->height,
PIX_FMT_YUV420P,
SWS_FAST_BILINEAR,
NULL, NULL, NULL);
videoFrame = avcodec_alloc_frame();



//5.讀取流內容並且解碼
//解碼器之後,就可以讀取一部分流中的數據(壓縮數據),然後將壓縮數據作為解碼器的輸入,解碼器將其解碼為原始數據(裸數據),之後就可以將原始數據寫入文件了
AVPacket packet;
int gotFrame = 0;
while(true) {
if(av_read_frame(formatContext, &packet)) {
// End Of File
break;

}
int packetStreamIndex = packet.stream_index;
if(packetStreamIndex == videoStreamIndex) {
int len = avcodec_decode_video2(videoCodecCtx, videoFrame,&gotFrame, &packet);
if(len < 0) {
break;

}
if(gotFrame) {
self->handleVideoFrame();

}

} else if(packetStreamIndex == audioStreamIndex) {
int len = avcodec_decode_audio4(audioCodecCtx, audioFrame,&gotFrame, &packet);
if(len < 0) {
break;
}
if(gotFrame) {
self->handleVideoFrame();

}

}
}


//7、處理解碼後的裸數據
//裸數據,音頻就是PCM數據,視頻就是YUV數據。下面將其處理成我們所需要的格式並且進行寫文件

//7.1 音頻裸數據處理
//將音頻裸數據直接寫入文件,比如寫入到文件audio.pcm中
void* audioData;
int numFrames;
if(swrContext) {
int bufSize = av_samples_get_buffer_size(NULL, channels,(int)(audioFrame->nb_samples * channels),AV_SAMPLE_FMT_S16, 1);
if (!_swrBuffer || _swrBufferSize < bufSize) {
swrBufferSize = bufSize;
swrBuffer = realloc(_swrBuffer, _swrBufferSize);

}
Byte *outbuf[2] = { _swrBuffer, 0 };
numFrames = swr_convert(_swrContext, outbuf,(int)(audioFrame->nb_samples * channels),(const uint8_t **)_audioFrame->data,audioFrame->nb_samples);
audioData = swrBuffer;

} else {
audioData = audioFrame->data[0];
numFrames = audioFrame->nb_samples;

}

//7.2視頻的裸數據的處理
//接收到YUV數據,寫入文件video.yuv中
uint8_t * luma;
uint8_t *chromaB;
uint8_t *chromaR;
if (videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P || videoCodecCtx->pix_fmt == av_pix+AV_PIX_FMT_YUVJ420P) {
luma = copyFrameData(videoFrame->data[0],
videoFrame->linesize[0],
videoCodecCtx->width,
videoCodecCtx->height);

chromaB = copyFrameData(videoFrame->data[1],
videoFrame->linesize[1],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);
chromaR = copyFrameData(videoFrame->data[2],
videoFrame->linesize[2],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);

}else{
sws_scale(_swsContext,
(const uint8_t **)videoFrame->data,
videoFrame->linesize,
0,

videoCodecCtx->height,
picture.data,
picture.linesize);
luma = copyFrameData(picture.data[0],
picture.linesize[0],
videoCodecCtx->width,
videoCodecCtx->height);
chromaB = copyFrameData(picture.data[1],
picture.linesize[1],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);
chromaR = copyFrameData(picture.data[2],
picture.linesize[2],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);

}

//8、關閉所有的資源
//退出的時候,要將用到的FFmpeg框架中的資源,包括FFmpeg框架對外的連接資源等全都釋放掉
//清空所有的音頻資源
if (swrBuffer) {
free(swrBuffer);
swrBuffer = NULL;
swrBufferSize = 0;
}
if (swrContext) {
swr_free(&swrContext);
swrContext = NULL;
}
if (audioFrame) {
av_free(audioFrame);
audioFrame = NULL;
}
if (audioCodecCtx) {
avcodec_close(audioCodecCtx);
audioCodecCtx = NULL;
}
//清空所有的視頻資源
if (swsContext) {
sws_freeContext(swsContext);
swsContext = NULL;
}
if (pictureValid) {

avpicture_free(&picture);
pictureValid = false;
}
if (videoFrame) {
av_free(videoFrame);
videoFrame = NULL;
}
if (videoCodecCtx) {
avcodec_close(videoCodecCtx);
videoCodecCtx = NULL;
}

//關閉連接資源
if (formatCtx) {
avformat_close_input(&formatCtx);
formatCtx = NULL;
}
/<formatctx->
  • 註冊協議、格式與編解碼器
  • 打開媒體文件源,並設置超時回調(本地磁盤文件也可能是網絡媒體資源鏈接,會涉及RTMP/HTTP等協議的視頻源,設置超時時間)
  • 尋找各個流(音頻視頻流),並且打開對應的解碼器(音頻視頻解碼器)
  • 初始化解碼後數據的結構體 需要分配出解碼之後的數據所存放的內存空間 進行格式轉換所需要用到的對象
  • 讀取流內容並且解碼。解碼器之後,就可以讀取一部分流中的數據(壓縮數據),然後將壓縮數據作為解碼器的輸入,解碼器將其解碼為原始數據(裸數據),之後就可以將原始數據寫入文件了
  • 處理解碼後的裸數據。裸數據,音頻就是PCM數據,視頻就是YUV數據。下面將其處理成我們所需要的格式並且進行寫文件 音頻裸數據處理,將音頻裸數據直接寫入文件,比如寫入到文件audio.pcm中 視頻的裸數據的處理,接收到YUV數據,寫入文件video.yuv中
  • 關閉所有的資源 退出的時候,要將用到的FFmpeg框架中的資源,包括FFmpeg框架對外的連接資源等全都釋放掉

FFmpeg主要的文件libavformat、libavcodec

libavformat和libavcodec

音視頻開發 - FFmpeg

libavformate主要架構

AVFormatContext是API層直接接觸到的結構體,它會進行格式的封裝與解封裝,它的數據部分

由底層提供,底層使用了AVIOContext,這個AVIOContext實際上就是為普通的I/O增加了一層

Buffer緩衝區,再往底層就是URLContext,也就是到達了協議層,協議層的具體實現有很多,包

括rtmp、http、hls、file等,這就是libavformat的內部封裝了。

音視頻開發 - FFmpeg

libavcodec主要架構

這一層我們能接觸到的最頂層的結構體就是AVCodecContext,該結構體包含的就是與實際的

編解碼有關的部分。首先,AVCodecContext是包含在一個AVStream裡面的,即描述了這路流的

編碼格式是什麼,其中存放了具體的編碼格式信息,根據Codec的信息可以打開編碼器或者解碼

器,然後利用該編碼器或者解碼器進行AVPacket與AVFrame之間的轉換(實際上就是解碼或者

編碼的過程),這是FFmpeg中最重要的一部分。

FFmpegAPI

1、通用API

  • av_register_all分析
    內部實現會先調用avcodec_register_all來註冊所有config.h裡面開放的編解碼器,然後會註冊所有的Muxer和Demuxer(也就是封裝格式),最後註冊所有的Protocol(即協議層的東西)。這樣一來,在configure過程中開啟(enable)或者關閉(disable)的選項就就作用到了運行時,該函數的源碼分析涉及的源碼文件包括:url.c、allformats.c、mux.c、format.c等文件
  • av_find_codec分析
    尋找解碼器,一部分是尋找編碼器
  • avcodec_open2分析
    打開編解碼器(Codec)的函數
  • avcodec_close分析
    close就是一個逆過程,找到對應的實現文件中的close函數指針所指向的函數,然後該函數會調用對應第三方庫的API來關閉掉對應的編碼庫

2、FFmpeg解碼API

  • avformat_open_input分析
    avformat_open_input會根據所提供的文件路徑判斷文件的格式,其實就是通過這一步來決定使用的到底是哪一個Demuxer
  • avformat_find_stream_info分析
    直播場景下的拉流客戶端中“秒開首屏”,就是與該函數分析的代碼實現息息相關的
  • avcodec_decode分析
    一部分是解碼視頻,一部分是解碼音頻
  • avformat_close_input分析
    首先會調用對應的Demuxer中的生命週期read_close方法,然後釋放掉AVFormatContext,最後關閉文件或者遠程網絡連接。

3、FFmpeg編碼API

  • avformat_alloc_output_context2分析
    調用方法avformat_alloc_context來分配一個AVFormatContext結構體,當然最關鍵的還是根據上一步註冊的Muxer和Demuxer部分(也就是封裝格式部分)去找到對應的格式

更多音視頻免費視頻資料獲取 後臺私信【音視頻】

音視頻開發 - FFmpeg


分享到:


相關文章: