術(shù)語
容器/文件(Conainer/File):即特定格式的多媒體文件慎璧,比如 MP4偷溺、flv逮栅、mov等悴势。
媒體流(Stream):表示時間軸上的一段連續(xù)數(shù)據(jù),如一段聲音數(shù)據(jù)措伐、一段視頻數(shù)據(jù)或一段字幕數(shù)據(jù)特纤,可以是壓縮的,也可以是非壓縮的侥加,壓縮的數(shù)據(jù)需要關(guān)聯(lián)特定的編解碼器捧存。
數(shù)據(jù)幀/數(shù)據(jù)包(Frame/Packet):通常,一個媒體流是由大量的數(shù)據(jù)幀組成的担败,對于壓縮數(shù)據(jù)昔穴,幀對應(yīng)著編解碼器的最小處理單元,分屬于不同媒體流的數(shù)據(jù)幀交錯存儲于容器之中提前。
編解碼器:編解碼器是以幀(針對視頻吗货,音頻沒有幀的概念,可以理解為一段音頻數(shù)據(jù))為單位實現(xiàn)壓縮數(shù)據(jù)和原始數(shù)據(jù)之間的相互轉(zhuǎn)換的狈网。
解碼過程
1.導(dǎo)入頭文件宙搬。
#import <libavformat/avformat.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>
#import <libavutil/pixdesc.h>
#include <libavutil/imgutils.h>
2.注冊協(xié)議、格式拓哺、編解碼器勇垛。
調(diào)用FFmpeg的注冊協(xié)議、格式與編解碼器的方法士鸥,確保所有的格式與編解碼器都被注冊到了FFmpeg框架中闲孤。
av_register_all(); //新庫已經(jīng)廢棄了這個函數(shù),不再需要注冊
3.創(chuàng)建上下文烤礁,打開媒體文件源讼积。
注冊了格式以及編解碼器之后,接下來就應(yīng)該打開對應(yīng)的媒體文件了鸽凶,當然該文件既可能是本地磁盤的文件币砂,也可能是網(wǎng)絡(luò)媒體資源的一個鏈接,如果是網(wǎng)絡(luò)鏈接玻侥,則會涉及不同的協(xié)議,比如RTMP亿蒸、HTTP等協(xié)議的視頻源凑兰。
AVFormatContext *formatCtx = avformat_alloc_context();
avformat_open_input(&formatCtx, path, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);
4.尋找各個流掌桩,并且打開對應(yīng)的解碼器。
這一步我們要尋找出各個流姑食,然后找到流中對應(yīng)的解碼器波岛,并且打開它。
尋找音視頻流:
for(int i = 0; i < formatCtx->nb_streams; i++) {
AVStream* stream = formatCtx->streams[i];
if(AVMEDIA_TYPE_VIDEO == stream->codec->codec_type) {
// 視頻流
videoStreamIndex = i;
} else if(AVMEDIA_TYPE_AUDIO == stream->codec->codec_type ) {
// 音頻流
audioStreamIndex = i;
}
}
打開音頻流解碼器:
AVCodecContext * audioCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(audioCodecCtx, audioStream->codecpar);
AVCodec *codec = avcodec_find_decoder(audioCodecCtx ->codec_id);
if(!codec) {
// 找不到對應(yīng)的音頻解碼器
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
// 打開音頻解碼器失敗
}
打開視頻流解碼器:
AVCodecContext *videoCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodecCtx, videoStream->codecpar);
AVCodec *codec = avcodec_find_decoder(videoCodecCtx->codec_id);
if(!codec) {
// 找不到對應(yīng)的視頻解碼器
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
// 打開視頻解碼器失敗
}
5.初始化解碼后數(shù)據(jù)的結(jié)構(gòu)體音半。
分配出解碼之后的數(shù)據(jù)所存放的內(nèi)存空間则拷,以及進行格式轉(zhuǎn)換需要用到的對象。
構(gòu)建音頻的格式轉(zhuǎn)換對象以及音頻解碼后數(shù)據(jù)存放的對象:
SwrContext *swrContext = NULL;
if(audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
// 如果不是我們需要的數(shù)據(jù)格式,則重新采樣
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 = av_frame_alloc();
}
構(gòu)建視頻的格式轉(zhuǎn)換對象以及視頻解碼后數(shù)據(jù)存放的對象:
AVFrame *videoFrame = avcodec_alloc_frame();
//創(chuàng)建緩沖區(qū)
av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
videoCodecCtx->width,
videoCodecCtx->height,
1);
//開辟一塊內(nèi)存空間
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//緩沖區(qū)數(shù)據(jù)填充
av_image_fill_arrays(avframe_yuv420p->data,
avframe_yuv420p->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
videoCodecCtx->width,
videoCodecCtx->height,
1);
//初始化視頻數(shù)據(jù)存儲對象
swsContext = sws_getContext(swsContext, videoCodecCtx->width,videoCodecCtx->height, videoCodecCtx->pix_fmt, videoCodecCtx->width, videoCodecCtx->height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR,NULL, NULL, NULL);
6.讀取流內(nèi)容并且解碼曹鸠。
讀取一部分流中的數(shù)據(jù)(壓縮數(shù)據(jù))煌茬, 然后將壓縮數(shù)據(jù)作為解碼器的輸入,解碼器將其解碼為原始數(shù)據(jù)(裸數(shù) 據(jù))彻桃,之后就可以將原始數(shù)據(jù)寫入文件了:
AVPacket packet;
while(true) {
if(av_read_frame(formatContext, &packet)) { // End Of File
break;
}
int packetStreamIndex = packet.stream_index;
if(packetStreamIndex == videoStreamIndex) {
int send_result = avcodec_send_packet(videoCodecCtx, packet);
avcodec_receive_frame(videoCodecCtx, videoFrame);
if(send_result == 0) {
//處理視頻數(shù)據(jù)坛善,見【7.】
}
if(gotFrame) {
self->handleVideoFrame();
}
} else if(packetStreamIndex == audioStreamIndex) {
int send = avcodec_send_packet(audioCodecCtx, packet);
avcodec_receive_frame(audioCodecCtx, audioFrame);
if(send == 0) {
//處理音頻數(shù)據(jù),見【7.】
}
}
}
7.處理解碼后的裸數(shù)據(jù)邻眷。
解碼之后會得到裸數(shù)據(jù)眠屎,音頻就是PCM數(shù)據(jù),視頻就是YUV數(shù)據(jù)肆饶。下面將其處理成我們所需要的格式并且進行寫文件改衩。 音頻裸數(shù)據(jù)的處理:
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;
}
接收到音頻裸數(shù)據(jù)之后,就可以直接寫文件了驯镊,比如寫到文件 audio.pcm中葫督。
視頻裸數(shù)據(jù)的處理:
uint8_t* luma;
uint8_t* chromaB;
uint8_t* chromaR;
if(videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P ||
videoCodecCtx->pix_fmt == 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);
}
接收到Y(jié)UV數(shù)據(jù)之后也可以直接寫入文件了,比如寫到文件 video.yuv中阿宅。
8.關(guān)閉所有資源候衍。
解碼結(jié)束或中途退出需要將用到的FFmpeg框架中的資源,包括 FFmpeg框架對外的連接資源等全都釋放掉洒放。
關(guān)閉音頻資源:
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;
}
關(guān)閉視頻資源:
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;
}
關(guān)閉連接資源:
if (formatCtx) {
avformat_close_input(&formatCtx);
formatCtx = NULL;
}