內容一:FFmpeg-命令行補充?
案例:視頻轉為高質量 GIF 動圖?
命令:./ffmpeg -ss 00:00:03 -t 3 -i Test.mov -s 640x360 -r 15 dongtu.gif
解釋:
1、ffmpeg 是你剛才安裝的程序;
2、-ss 00:00:03 表示從第 00 分鐘 03 秒開始制作 GIF,如果你想從第 9 秒開始,則輸入 -ss 00:00:09,或者 -ss 9,支持小數點,所以也可以輸入 -ss 00:00:11.3,或者 -ss 34.6 之類的,如果不加該命令,則從 0 秒開始制作;
3、-t 3 表示把持續(xù) 3 秒的視頻轉換為 GIF,你可以把它改為其他數字,例如 1.5,7 等等,時間越長,GIF 體積越大,如果不加該命令,則把整個視頻轉為 GIF;
4、-i 表示 invert 的意思,轉換;
5、Test.mov 就是你要轉換的視頻,名稱最好不要有中文,不要留空格,支持多種視頻格式;
6、-s 640x360 是 GIF 的分辨率,視頻分辨率可能是 1080p,但你制作的 GIF 可以轉為 720p 等,允許自定義,分辨率越高體積越大,如果不加該命令,則保持分辨率不變;
7、-r 15 表示幀率,網上下載的視頻幀率通常為 24,設為 15 效果挺好了,幀率越高體積越大,如果不加該命令,則保持幀率不變;
8、dongtu.gif:就是你要輸出的文件,你也可以把它命名為 hello.gif 等等。
內容二:FFmpeg-視頻解碼?
>內容:將音視頻解碼在Android平臺進行實現(xiàn)
>作業(yè):然后移植到iOS平臺(提示:將iOS環(huán)境搭建好,然后童鞋們填寫代碼)
功能實現(xiàn)是一模一樣,沒有區(qū)別?
下一節(jié)課講解?寫好了直接發(fā)到我的郵箱?
第一點:確定音視頻編解碼流程學習?
第一步:組冊組件
av_register_all()
例如:編碼器、解碼器等等…
第二步:打開封裝格式->打開文件
例如:.mp4、.mov、.wmv文件等等...
avformat_open_input();
第三步:查找視頻流
如果是視頻解碼,那么查找視頻流,如果是音頻解碼,那么就查找音頻流
avformat_find_stream_info();
第四步:查找視頻解碼器
1、查找視頻流索引位置
2、根據視頻流索引,獲取解碼器上下文
3、根據解碼器上下文,獲得解碼器ID,然后查找解碼器
第五步:打開解碼器
avcodec_open2();
第六步:讀取視頻壓縮數據->循環(huán)讀取
每讀取一幀數據,立馬解碼一幀數據
第七步:視頻解碼->播放視頻->得到視頻像素數據
第八步:關閉解碼器->解碼完成
第二點:實現(xiàn)功能->寫代碼
#include <jni.h>
#include <string>
#include <android/log.h>
//特殊處理:在安卓底層開發(fā)中,需要注意的
//因為要支持C/C++混合編程
extern "C" {
//核心庫
#include "libavcodec/avcodec.h"
//封裝格式處理庫
#include "libavformat/avformat.h"
//工具庫
#include "libavutil/imgutils.h"
//視頻像素數據格式庫
#include "libswscale/swscale.h"
//視頻解碼
JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(JNIEnv *env, jobject jobj,
jstring jinFilePath,
jstring joutFilePath);
}
JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(
JNIEnv *env,
jobject jobj, jstring jinFilePath, jstring joutFilePath) {
//第一步:組冊組件
av_register_all();
//第二步:打開封裝格式->打開文件
//參數一:封裝格式上下文
//作用:保存整個視頻信息(解碼器、編碼器等等...)
//信息:碼率、幀率等...
AVFormatContext* avformat_context = avformat_alloc_context();
//參數二:視頻路徑
const char *url = env->GetStringUTFChars(jinFilePath, NULL);
//在我們iOS里面
//NSString* path = @"/user/dream/test.mov";
//const char *url = [path UTF8String]
//參數三:指定輸入的格式
//參數四:設置默認參數
int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
if (avformat_open_input_result != 0){
//安卓平臺下log
__android_log_print(ANDROID_LOG_INFO, "main", "打開文件失敗");
//iOS平臺下log
//NSLog("打開文件失敗");
//不同的平臺替換不同平臺log日志
return;
}
//第三步:查找視頻流->拿到視頻信息
//參數一:封裝格式上下文
//參數二:指定默認配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
if (avformat_find_stream_info_result < 0){
__android_log_print(ANDROID_LOG_INFO, "main", "查找失敗");
return;
}
//第四步:查找視頻解碼器
//1、查找視頻流索引位置
int av_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//判斷流類型:視頻流、音頻流、字母流等等...
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
av_stream_index = i;
break;
}
}
//2、根據視頻流索引,獲取解碼器上下文
AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
//3、根據解碼器上下文,獲得解碼器ID,然后查找解碼器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打開解碼器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0){
__android_log_print(ANDROID_LOG_INFO, "main", "打開解碼器失敗");
return;
}
//測試一下
//打印信息
__android_log_print(ANDROID_LOG_INFO, "main", "解碼器名稱:%s", avcodec->name);
//第六步:讀取視頻壓縮數據->循環(huán)讀取
//1、分析av_read_frame參數
//參數一:封裝格式上下文
//參數二:一幀壓縮數據 = 一張圖片
//av_read_frame()
//結構體大小計算:字節(jié)對齊原則
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用于解碼操作)
//開辟一塊內存空間
AVFrame* avframe_in = av_frame_alloc();
int decode_result = 0;
//4、注意:在這里我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式
//參數一:源文件->原始視頻像素數據格式寬
//參數二:源文件->原始視頻像素數據格式高
//參數三:源文件->原始視頻像素數據格式類型
//參數四:目標文件->目標視頻像素數據格式寬
//參數五:目標文件->目標視頻像素數據格式高
//參數六:目標文件->目標視頻像素數據格式類型
SwsContext *swscontext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,
NULL,
NULL);
//創(chuàng)建一個yuv420視頻像素數據格式緩沖區(qū)(一幀數據)
AVFrame* avframe_yuv420p = av_frame_alloc();
//給緩沖區(qū)設置類型->yuv420類型
//得到YUV420P緩沖區(qū)大小
//參數一:視頻像素數據格式類型->YUV420P格式
//參數二:一幀視頻像素數據寬 = 視頻寬
//參數三:一幀視頻像素數據高 = 視頻高
//參數四:字節(jié)對齊方式->默認是1
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
//開辟一塊內存空間
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//向avframe_yuv420p->填充數據
//參數一:目標->填充數據(avframe_yuv420p)
//參數二:目標->每一行大小
//參數三:原始數據
//參數四:目標->格式類型
//參數五:寬
//參數六:高
//參數七:字節(jié)對齊方式
av_image_fill_arrays(avframe_yuv420p->data,
avframe_yuv420p->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
int y_size, u_size, v_size;
//5.2 將yuv420p數據寫入.yuv文件中
//打開寫入文件
const char *outfile = env->GetStringUTFChars(joutFilePath, NULL);
FILE* file_yuv420p = fopen(outfile, "wb+");
if (file_yuv420p == NULL){
__android_log_print(ANDROID_LOG_INFO, "main", "輸出文件打開失敗");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, packet) >= 0){
//>=:讀取到了
//<0:讀取錯誤或者讀取完畢
//2、是否是我們的視頻流
if (packet->stream_index == av_stream_index){
//第七步:解碼
//學習一下C基礎,結構體
//3、解碼一幀壓縮數據->得到視頻像素數據->yuv格式
//采用新的API
//3.1 發(fā)送一幀視頻壓縮數據
avcodec_send_packet(avcodec_context, packet);
//3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用于解碼操作)
decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
if (decode_result == 0){
//解碼成功
//4、注意:在這里我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式
//視頻像素數據格式很多種類型: yuv420P、yuv422p、yuv444p等等...
//保證:我的解碼后的視頻像素數據格式統(tǒng)一為yuv420P->通用的格式
//進行類型轉換: 將解碼出來的視頻像素點數據格式->統(tǒng)一轉類型為yuv420P
//sws_scale作用:進行類型轉換的
//參數一:視頻像素數據格式上下文
//參數二:原來的視頻像素數據格式->輸入數據
//參數三:原來的視頻像素數據格式->輸入畫面每一行大小
//參數四:原來的視頻像素數據格式->輸入畫面每一行開始位置(填寫:0->表示從原點開始讀取)
//參數五:原來的視頻像素數據格式->輸入數據行數
//參數六:轉換類型后視頻像素數據格式->輸出數據
//參數七:轉換類型后視頻像素數據格式->輸出畫面每一行大小
sws_scale(swscontext,
(const uint8_t *const *)avframe_in->data,
avframe_in->linesize,
0,
avcodec_context->height,
avframe_yuv420p->data,
avframe_yuv420p->linesize);
//方式一:直接顯示視頻上面去
//方式二:寫入yuv文件格式
//5、將yuv420p數據寫入.yuv文件中
//5.1 計算YUV大小
//分析一下原理?
//Y表示:亮度
//UV表示:色度
//有規(guī)律
//YUV420P格式規(guī)范一:Y結構表示一個像素(一個像素對應一個Y)
//YUV420P格式規(guī)范二:4個像素點對應一個(U和V: 4Y = U = V)
y_size = avcodec_context->width * avcodec_context->height;
u_size = y_size / 4;
v_size = y_size / 4;
//5.2 寫入.yuv文件
//首先->Y數據
fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
//其次->U數據
fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
//再其次->V數據
fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
current_index++;
__android_log_print(ANDROID_LOG_INFO, "main", "當前解碼第%d幀", current_index);
}
}
}
//第八步:釋放內存資源,關閉解碼器
av_packet_free(&packet);
fclose(file_yuv420p);
av_frame_free(&avframe_in);
av_frame_free(&avframe_yuv420p);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
實例工程
iOS工程 https://pan.baidu.com/s/1dFlhDdB
Android工程 https://pan.baidu.com/s/1jHAzHzw