第一點:分析視頻編碼原理?->流程?
流程整理
第一步:注冊組件->編碼器浆兰、解碼器等等…
第二步:初始化封裝格式上下文
第三步:打開輸入文件
第四步:創(chuàng)建輸出碼流->視頻流->今后設(shè)置->設(shè)置為視頻流
第五步:查找視頻編碼器
第六步:打開視頻編碼器
第七步:寫入文件頭信息(有些文件頭信息)->一般情況下都會有
第八步:循環(huán)編碼視頻像素數(shù)據(jù)->視頻壓縮數(shù)據(jù)
第九步:將編碼后的視頻壓縮數(shù)據(jù)寫入文件中
第十步:輸入像素數(shù)據(jù)讀取完畢后回調(diào)函數(shù)
作用:輸出編碼器中剩余AVPacket
第十一步:寫入文件尾部信息
第十二步:釋放內(nèi)存,關(guān)閉編碼器等等…
代碼部分
#include <jni.h>
#include <string>
#include "android/log.h"
extern "C"{
//導入音視頻頭文件庫
//核心庫
#include "libavcodec/avcodec.h"
//封裝格式處理庫
#include "libavformat/avformat.h"
//工具庫
#include "libavutil/imgutils.h"
//視頻像素數(shù)據(jù)格式庫
#include "libswscale/swscale.h"
JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_android_video_encode_MainActivity_ffmpegVideoEncode(
JNIEnv *env, jobject jobj, jstring jinFilePath, jstring joutFilePath);
}
//視頻編碼
JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_android_video_encode_MainActivity_ffmpegVideoEncode(
JNIEnv *env, jobject jobj, jstring jinFilePath, jstring joutFilePath) {
//第一步:注冊組件->編碼器、解碼器等等…
av_register_all();
//第二步:初始化封裝格式上下文->視頻編碼->處理為視頻壓縮數(shù)據(jù)格式
AVFormatContext* avformat_context = avformat_alloc_context();
//注意事項:FFmepg程序推測輸出文件類型->視頻壓縮數(shù)據(jù)格式類型
const char* coutFilePath = env->GetStringUTFChars(joutFilePath, NULL);
//得到視頻壓縮數(shù)據(jù)格式類型(h264买雾、h265藻丢、mpeg2等等...)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
//指定類型
avformat_context->oformat = avoutput_format;
//第三步:打開輸出文件
//參數(shù)一:輸出流
//參數(shù)二:輸出文件
//參數(shù)三:權(quán)限->輸出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0){
__android_log_print(ANDROID_LOG_INFO, "main", "打開輸出文件失敗");
return;
}
//第四步:創(chuàng)建輸出碼流->創(chuàng)建了一塊內(nèi)存空間->并不知道他是什么類型流->希望他是視頻流
AVStream* av_video_stream = avformat_new_stream(avformat_context, NULL);
//第五步:查找視頻編碼器
//1剪撬、獲取編碼器上下文
AVCodecContext *avcodec_context = av_video_stream->codec;
//2、設(shè)置編解碼器上下文參數(shù)->必需設(shè)置->不可少
//目標:設(shè)置為是一個視頻編碼器上下文->指定的是視頻編碼器
//上下文種類:視頻解碼器悠反、視頻編碼器残黑、音頻解碼器、音頻編碼器
//2.1 設(shè)置視頻編碼器ID
avcodec_context->codec_id = avoutput_format->video_codec;
//2.2 設(shè)置編碼器類型->視頻編碼器
//視頻編碼器->AVMEDIA_TYPE_VIDEO
//音頻編碼器->AVMEDIA_TYPE_AUDIO
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
//2.3 設(shè)置讀取像素數(shù)據(jù)格式->編碼的是像素數(shù)據(jù)格式->視頻像素數(shù)據(jù)格式->YUV420P(YUV422P斋否、YUV444P等等...)
//注意:這個類型是根據(jù)你解碼的時候指定的解碼的視頻像素數(shù)據(jù)格式類型
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
//2.4 設(shè)置視頻寬高->視頻尺寸
avcodec_context->width = 640;
avcodec_context->height = 352;
//2.5 設(shè)置幀率->表示每秒25幀
//視頻信息->幀率 : 25.000 fps
//f表示:幀數(shù)
//ps表示:時間(單位:每秒)
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;
//2.6 設(shè)置碼率
//2.6.1 什么是碼率梨水?
//含義:每秒傳送的比特(bit)數(shù)單位為 bps(Bit Per Second),比特率越高茵臭,傳送數(shù)據(jù)速度越快疫诽。
//單位:bps,"b"表示數(shù)據(jù)量旦委,"ps"表示每秒
//目的:視頻處理->視頻碼率
//2.6.2 什么是視頻碼率?
//含義:視頻碼率就是數(shù)據(jù)傳輸時單位時間傳送的數(shù)據(jù)位數(shù)奇徒,一般我們用的單位是kbps即千位每秒
//視頻碼率計算如下?
//基本的算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)
//例如:Test.mov時間 = 24缨硝,文件大小(視頻+音頻) = 1.73MB
//視頻大小 = 1.34MB(文件占比:77%) = 1.34MB * 1024 * 1024 * 8 = 字節(jié)大小 = 468365字節(jié) = 468Kbps
//音頻大小 = 376KB(文件占比:21%)
//計算出來值->碼率 : 468Kbps->表示1000摩钙,b表示位(bit->位)
//總結(jié):碼率越大,視頻越大
avcodec_context->bit_rate = 468000;
//2.7 設(shè)置GOP->影響到視頻質(zhì)量問題->畫面組->一組連續(xù)畫面
//MPEG格式畫面類型:3種類型->分為->I幀追葡、P幀腺律、B幀
//I幀->內(nèi)部編碼幀->原始幀(原始視頻數(shù)據(jù))
// 完整畫面->關(guān)鍵幀(必需的有,如果沒有I宜肉,那么你無法進行編碼匀钧,解碼)
// 視頻第1幀->視頻序列中的第一個幀始終都是I幀,因為它是關(guān)鍵幀
//P幀->向前預測幀->預測前面的一幀類型谬返,處理數(shù)據(jù)(前面->I幀之斯、B幀)
// P幀數(shù)據(jù)->根據(jù)前面的一幀數(shù)據(jù)->進行處理->得到了P幀
//B幀->前后預測幀(雙向預測幀)->前面一幀和后面一幀
// B幀壓縮率高,但是對解碼性能要求較高遣铝。
//總結(jié):I只需要考慮自己 = 1幀佑刷,P幀考慮自己+前面一幀 = 2幀,B幀考慮自己+前后幀 = 3幀
// 說白了->P幀和B幀是對I幀壓縮
//每250幀酿炸,插入1個I幀瘫絮,I幀越少,視頻越小->默認值->視頻不一樣
avcodec_context->gop_size = 250;
//2.8 設(shè)置量化參數(shù)->數(shù)學算法(高級算法)->不講解了
//總結(jié):量化系數(shù)越小填硕,視頻越是清晰
//一般情況下都是默認值麦萤,最小量化系數(shù)默認值是10鹿鳖,最大量化系數(shù)默認值是51
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;
//2.9 設(shè)置b幀最大值->設(shè)置不需要B幀
avcodec_context->max_b_frames = 0;
//第二點:查找編碼器->h264
//找不到編碼器->h264
//重要原因是因為:編譯庫沒有依賴x264庫(默認情況下FFmpeg沒有編譯進行h264庫)
//第一步:編譯h264庫
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if (avcodec == NULL){
__android_log_print(ANDROID_LOG_INFO, "main", "找不到解碼器");
return;
}
__android_log_print(ANDROID_LOG_INFO, "main", "解碼器名稱為:%s", avcodec->name);
}
第二點:視頻編碼->實現(xiàn)功能->yuv編碼為h264
流程整理
yuv:視頻像素數(shù)據(jù)格式
h264:視頻壓縮數(shù)據(jù)格式
1、查找編碼器壮莹?
獲取編碼器名稱
找不到編碼器->h264
重要原因是因為:編譯庫沒有依賴x264庫(默認情況下)
第一步:下載x264庫
通過git下載:git clone git://git.videolan.org/x264.git
第二步:解壓這個庫
第三步:編寫腳本->編譯x264的.a靜態(tài)庫
指定編譯平臺類型:iOS平臺翅帜、安卓平臺、Mac平臺命满、Windows平臺等等…
編寫Android平臺.a靜態(tài)庫->課前準備好了->研究一下
第四步:編譯Android動態(tài)庫->編譯FFmpeg>修改腳本文件涝滴?
加入x264庫,將其編譯進去
代碼整理
build_x264_android.sh
#!/bin/bash
#進入x264庫
cd x264
#指定編譯平臺->Android平臺.a靜態(tài)庫
#指定NDK目錄
NDK_DIR=/Users/yangshaohong/Desktop/tools/eclipse/android-ndk/android-ndk-r10e
#指定編譯的x264平臺架構(gòu)類型->arm架構(gòu)->系統(tǒng)版本
SYSROOT=$NDK_DIR/platforms/android-18/arch-arm
#指定鏈接工具->Android平臺下arm連接器
TOOLCHAIN=$NDK_DIR/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
#指定輸出編譯好的.a靜態(tài)庫存放路徑
PREFIX=/Users/yangshaohong/Desktop/ffmpeg-android/android-build-x264
ADDI_CFLAGS="-marm"
#設(shè)置編譯參數(shù)
function build_h264
{
./configure \
--prefix=$PREFIX \
--host=arm-linux \
--enable-static \
--disable-asm \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
}
#執(zhí)行腳本
build_h264
#安裝編譯動態(tài)庫
sudo make install
echo "Android h264 builds finished"
build-ffmpeg-armeabi-x264.sh
#!/bin/bash
#第一步:進入到指定目錄
cd ffmpeg-3.4
#第二步:指定NDK路徑(編譯什么樣的平臺->采用什么樣的平臺編譯器)
#Android平臺NDK技術(shù)->做C/C++開發(fā)->編譯Andrroid平臺下.so動態(tài)庫
#注意:放在英文目錄(中文目錄報錯)
#修改一:修改為你自己NDK存放目錄
NDK_DIR=/Users/yangshaohong/Desktop/tools/eclipse/android-ndk/android-ndk-r10e
#第三步:配置Android系統(tǒng)版本(支持最小的版本)
#指定使用NDK Platform版本(對應(yīng)系統(tǒng)版本)
SYSROOT=$NDK_DIR/platforms/android-18/arch-arm
#第四步:指定編譯工具鏈->(通俗:指定編譯器)->CPU架構(gòu)(Android手機通用的CPU架構(gòu)類型):armeabi
TOOLCHAIN=$NDK_DIR/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
#第五步:指定CPU平臺架構(gòu)類型
#指定編譯后的安裝目錄
ARCH=arm
ADDI_CFLAGS="-marm"
#第六步:指定編譯成功之后胶台,.so動態(tài)庫存放位置
#修改二:這個目錄你需要修改為你自己目錄
PREFIX=/Users/yangshaohong/Desktop/ffmpeg-android/android-build/$ARCH
#第七步:編寫執(zhí)行編譯腳本->調(diào)用FFmpeg進行配置
#定義了Shell腳本函數(shù)(方法)
#編譯一部分(編譯:編解碼庫->核心庫歼疮、工具庫、視頻像素數(shù)據(jù)處理庫概作、音頻采樣數(shù)據(jù)處理庫等等...)
#你是如何知道這些庫需要編譯腋妙,那個庫不需要編譯?
#教你方法?
#有兩種方式你可以查看
#方式一:命令行查看
#方式二:通過打開文件查看
function build_armeabi
{
./configure \
--prefix=$PREFIX \
--target-os=android \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--enable-small \
#需要
--enable-gpl \
#禁用所有編碼器
--disable-encoders \
#通過libx264庫啟用H.264編碼
--enable-libx264 \
#啟用編碼器名稱
--enable-encoder=libx264 \
# 啟用幾個圖片編碼讯榕,由于生成視頻預覽
--enable-encoder=mjpeg \
--enable-encoder=png \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--arch=$ARCH \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
#和FFmpeg動態(tài)庫一起編譯骤素,指定你之前編譯好的x264靜態(tài)庫和頭文件
--extra-cflags="-I/Users/yangshaohong/Desktop/ffmpeg-android/android-build-x264/include" \
--extra-ldflags="-L/Users/yangshaohong/Desktop/ffmpeg-android/android-build-x264/lib" \
--enable-pic \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4
make install
}
#第八步:執(zhí)行函數(shù)->開始編譯
build_armeabi
echo "Android armeabi builds finished"
實例工程
腳本部分 https://pan.baidu.com/s/1hsb8dSS
工程部分 https://pan.baidu.com/s/1pLNwYc7