官方文檔
FFmpeg是一套可以用來(lái)記錄鸯两、轉(zhuǎn)換數(shù)字音頻蒸苇、視頻咬像,并能將其轉(zhuǎn)化為流的開(kāi)源計(jì)算機(jī)程序沮峡。它提供了錄制疚脐、轉(zhuǎn)換以及流化音視頻的完整解決方案。
FFmpeg的代碼是包括兩部分的邢疙,一部分是library棍弄,一部分是tool。api都是在library里面疟游,如果直接調(diào)api來(lái)操作視頻的話呼畸,就需要寫(xiě)c或者c++了。另一部分是tool颁虐,使用的是命令行蛮原,則不需要自己去編碼來(lái)實(shí)現(xiàn)視頻操作的流程。
各模塊的功能:
libavformat:用于各種音視頻封裝格式的生成和解析聪廉;
libavcodec:用于各種類型聲音瞬痘、圖像編解碼故慈;
libavutil:包含一些公共的工具函數(shù);
libswscale:用于視頻場(chǎng)景比例縮放框全、色彩映射轉(zhuǎn)換察绷;
libpostproc:用于后期效果處理;
ffmpeg:該項(xiàng)目提供的一個(gè)工具津辩,可用于格式轉(zhuǎn)換拆撼、解碼或電視卡即時(shí)編碼等;
ffsever:一個(gè) HTTP 多媒體即時(shí)廣播串流服務(wù)器喘沿;
ffplay:是一個(gè)簡(jiǎn)單的播放器闸度,使用ffmpeg 庫(kù)解析和解碼,通過(guò)SDL顯示蚜印;
本文主要介紹已經(jīng)編譯好FFmpeg庫(kù)的情況下怎么集成到iOS項(xiàng)目中并使用FFmpeg命令莺禁。
編譯教程,編譯好的庫(kù)內(nèi)容如下
導(dǎo)入iOS工程的配置及使用命令行工具的配置
1窄赋、編譯成功之后會(huì)得到FFmpeg-iOS這么一個(gè)目錄哟冬,里面有l(wèi)ib、include兩個(gè)子目錄忆绰,把FFmpeg-iOS直接拖進(jìn)工程里
3峰锁、設(shè)置 Header Search Paths 路徑萎馅,指向項(xiàng)目中include目錄
- 到這里command+B已經(jīng)可以編譯成功了,只使用FFmpeg API的話配置到這里就可以了虹蒋,下面關(guān)于使用命令行的配置基于FFmpeg4.2版本糜芳,不同的版本之間可能會(huì)存在一些小的差異
config.h文件在ffmpeg-4.2文件夾同級(jí)的scratch文件夾中峭竣,scratch下面有不同架構(gòu)的,真機(jī)選用arm64的即可
5晃虫、修改命令行工具的代碼
- 全局搜索這些頭文件皆撩,有引用的地方全部注釋
#include "compat/va_copy.h"
#include "libavresample/avresample.h"
#include "libpostproc/postprocess.h"
#include "libavutil/libm.h"
#include "libavutil/time_internal.h"
#include "libavutil/internal.h"
#include "libavformat/network.h"
#include "libavcodec/mathops.h"
#include "libavformat/os_support.h"
#include "libavutil/thread.h"
- ffmpeg.c文件中,注釋下面的函數(shù)調(diào)用 并導(dǎo)入系統(tǒng)頭文件 #include <pthread.h>
nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0],
ost->last_nb0_frames[1],
ost->last_nb0_frames[2]);
ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n",
ost->forced_keyframes_expr_const_values[FKF_N],
ost->forced_keyframes_expr_const_values[FKF_N_FORCED],
ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N],
ost->forced_keyframes_expr_const_values[FKF_T],
ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T],
res);
- cmdutils.c文件中print_all_libs_info函數(shù)里注釋以下兩行代碼
PRINT_LIB_INFO(avresample, AVRESAMPLE, flags, level);
PRINT_LIB_INFO(postproc, POSTPROC, flags, level);
- ffmpeg_opt.c文件中注釋以下兩行代碼
{ "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX },
{ "videotoolbox_pixfmt", HAS_ARG | OPT_STRING | OPT_EXPERT, { &videotoolbox_pixfmt}, "" },
- 解決mian函數(shù)重復(fù)的問(wèn)題,修改如下
ffmpeg.h 文件下增加函數(shù)聲明:
int ffmpeg_main(int argc, char **argv);
ffmpeg.c 文件中:
main函數(shù)修改為ffmpeg_main扛吞;主要是為了避免兩個(gè)main函數(shù)存在
- 修改執(zhí)行一次 ffmpeg_main 方法后 App閃退問(wèn)題
看到一些文章里說(shuō)修改cmdutils.c中的exit_program函數(shù)呻惕,注釋掉退出進(jìn)程的代碼,本人實(shí)測(cè)還是有問(wèn)題滥比,
最終修改是 ffmpeg.c文件中把所有調(diào)用 exit_program 函數(shù)的地方改為調(diào)用 ffmpeg_cleanup 函數(shù)就可以了亚脆。
注意:當(dāng)前全部改成ffmpeg_cleanup會(huì)出現(xiàn)的一些情況,比如執(zhí)行查看視頻信息的命令盲泛,這個(gè)命令里面沒(méi)有設(shè)置輸出路徑濒持,原來(lái)的代碼里走到判斷沒(méi)有輸出路徑的分支直接調(diào)用exit_program就退出了進(jìn)程,改成調(diào)用ffmpeg_cleanup后代碼會(huì)繼續(xù)往下走導(dǎo)致一些訪問(wèn)空指針的崩潰寺滚,這種個(gè)別情況自己再針對(duì)進(jìn)行處理就好了柑营,不過(guò)一般使用場(chǎng)景中都會(huì)有輸入和輸出
- 修改多次調(diào)用 ffmpeg_main 時(shí),訪問(wèn)空指針的問(wèn)題:
ffmpeg.c 文件中
在 ffmpeg_cleanup 方法中重置計(jì)數(shù)器村视,在 term_exit(); 這行代碼前添加重置計(jì)數(shù)的代碼
修改后如下:
nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;
term_exit();
以上這些都修改完官套,command+B,編譯成功蓖议!可以使用命令行了虏杰,在使用的地方引入頭文件 #import "ffmpeg.h"即可讥蟆。
命令行的使用及處理進(jìn)度回調(diào)
我這里封裝了一個(gè)使用cmd的方法勒虾,代碼如下
#import "HEFFmpegTools.h"
#import "ffmpeg.h"
@implementation HEFFmpegTools
///執(zhí)行ffmpeg指令," "為分割標(biāo)記符
+ (void)runCmd:(NSString *)commandStr completionBlock:(void(^)(int result))completionBlock {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 根據(jù) " " 將指令分割為指令數(shù)組
NSArray *argv_array = [commandStr componentsSeparatedByString:(@" ")];
// 將OC對(duì)象轉(zhuǎn)換為對(duì)應(yīng)的C對(duì)象
int argc = (int)argv_array.count;
char** argv = (char**)malloc(sizeof(char*)*argc);
for(int i=0; i < argc; i++) {
argv[i] = (char*)malloc(sizeof(char)*1024);
strcpy(argv[i],[[argv_array objectAtIndex:i] UTF8String]);
}
// 傳入指令數(shù)及指令數(shù)組,result==0表示成功
int result = ffmpeg_main(argc,argv);
NSLog(@"執(zhí)行FFmpeg命令:%@瘸彤,result = %d",commandStr,result);
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(result);
});
});
}
@end
使用runCmd函數(shù)的示例代碼修然,把一個(gè)視頻旋轉(zhuǎn)90度并保存到相冊(cè)
let videoPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/tempvideo.mp4"
let inputVideo = "\(Bundle.main.bundlePath)/ffm_video2.mp4"
let transformCmd = "ffmpeg -i \(inputVideo) -y -vf rotate=PI/2 \(videoPath)";
HEFFmpegTools.runCmd(transformCmd) { (result) in
if FileManager.default.fileExists(atPath: videoPath) {
print("保存到相冊(cè)");
UISaveVideoAtPathToSavedPhotosAlbum(videoPath, nil, nil, nil)
}
}
獲取處理進(jìn)度
1、任意創(chuàng)建一個(gè)Cocoa Touch Class 作為在.c里面引用的橋接文件质况,.h文件代碼全部刪掉愕宋,聲明函數(shù)
///獲取輸入源文件的時(shí)長(zhǎng)
void setDuration(long long duration);
///獲取當(dāng)前的處理進(jìn)度
void setCurrentTimeFromProgressInfo(char *progressInfo);
.m中刪除代碼只保留頭文件的引用,實(shí)現(xiàn)如下
#import "HEFFmpegBridge.h"
#import <Foundation/Foundation.h>
static long long totalDuration = 0;
void setDuration(long long duration) {
//duration的精度到微秒
//比如視頻長(zhǎng)度 00:00:24.53结榄,duration會(huì)是24533333
//比如視頻長(zhǎng)度 00:01:16.10中贝,duration會(huì)是76100000
printf("\n fileDuration = %lld\n",duration);
totalDuration = duration;
}
void setCurrentTimeFromProgressInfo(char *progressInfo) {
//progressInfo
//e.g. frame= 1968 fps=100 q=31.0 size= 4864kB time=00:01:06.59 bitrate= 598.3kbits/s speed=3.38x
//printf("\n ctime = %s\n",progressInfo);
NSString *progressStr = [NSString stringWithCString:progressInfo encoding:NSUTF8StringEncoding];
NSArray *infoArray = [progressStr componentsSeparatedByString:@" "];
NSString *timeString = @"";
for (NSString *info in infoArray) {
if ([info containsString:@"time"]) {
timeString = [info componentsSeparatedByString:@"="].lastObject;//e.g. 00:01:16.10,精確到十毫秒
}
}
NSArray *hmsArray = [timeString componentsSeparatedByString:@":"];
if (hmsArray.count != 3) {
return;
}
long long hours = [hmsArray[0] longLongValue];
long long minutes = [hmsArray[1] longLongValue];
long long seconds = 0;
long long mseconds = 0;
NSArray *tempArr = [hmsArray[2] componentsSeparatedByString:@"."];
if (tempArr.count == 2) {
seconds = [tempArr.firstObject longLongValue];
mseconds = [tempArr.lastObject longLongValue];
}
long long currentTime = (hours * 3600 + minutes * 60 + seconds) * 1000000 + mseconds * 10000;
double progress = [[NSString stringWithFormat:@"%.2f",currentTime * 1.0 / totalDuration] doubleValue];
NSLog(@"progress = %.2f",progress);
// //ffmpeg操作是在子線程中執(zhí)行的
// dispatch_async(dispatch_get_main_queue(), ^{
// //更新進(jìn)度UI
// });
}
從上面的代碼可以看到臼朗,處理進(jìn)度的回調(diào)主要是通過(guò)setDuration獲取輸入源文件的總時(shí)長(zhǎng)邻寿,setCurrentTimeFromProgressInfo獲取當(dāng)前已處理的時(shí)長(zhǎng),然后根據(jù)兩者比例計(jì)算出當(dāng)前的進(jìn)度视哑,細(xì)節(jié)直接看代碼
2绣否、什么時(shí)機(jī)調(diào)用這兩個(gè)函數(shù)?
- ffmpeg_opt.c文件中 open_input_file函數(shù)里在err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); 之后調(diào)用 setDuration(ic->duration);
- ffmpeg.c文件中 print_report函數(shù)里 fflush(stderr); 前調(diào)用 setCurrentTimeFromProgressInfo(buf.str);
看完這些覺(jué)得麻煩也可以直接用我編譯配置好的庫(kù)挡毅,導(dǎo)入可以直接使用 github地址蒜撮,如果覺(jué)得有幫助給個(gè)??吧
命令行使用的整理
命令基本格式:ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
常見(jiàn)的參數(shù)配置:
-f 強(qiáng)制指定編碼格式
-i 輸入源
-t 指定輸入輸出時(shí)長(zhǎng)
-r 指定幀率,即1S內(nèi)的幀數(shù)
-threads 指定線程數(shù)
-c:v 指定視頻的編碼格式
-ss 指定從什么時(shí)間開(kāi)始
-b:v 指定比特率
-b:v 2500k 指定輸出文件的視頻比特率為 2500kbit/s
-s 指定分辨率
-y 覆蓋輸出
-filter 指定過(guò)濾器
-vf 指定視頻過(guò)濾器
-an 指定去除對(duì)音頻的影響
-vn 指定去除對(duì)視頻的影響
-sn 指定去除對(duì)字幕的影響
-dn 指定去除對(duì)數(shù)據(jù)流的影響
-codec: copy 復(fù)制所有流而無(wú)需重新編碼
參考鏈接:
http://www.reibang.com/p/299906d4054d
http://www.reibang.com/p/cdd19047858e
https://cloud.tencent.com/developer/article/1764309