main()
int main(int argc, char **argv)
{
int ret;
BenchmarkTimeStamps ti;
/* 初始化動態(tài)加載 */
init_dynload();
/* 注冊退出回調(diào)函數(shù) */
register_exit(ffmpeg_cleanup);
/* 設(shè)置stderr的緩沖模式(win32運行時需要) */
setvbuf(stderr, NULL, _IONBF, 0);
/* 設(shè)置日志打印選項 */
av_log_set_flags(AV_LOG_SKIP_REPEATED);
parse_loglevel(argc, argv, options);
#if CONFIG_AVDEVICE
/* 注冊音視頻設(shè)備 */
avdevice_register_all();
#endif
/* 初始化網(wǎng)絡(luò)模塊 */
avformat_network_init();
/* 顯示ffmpeg的banner信息 */
show_banner(argc, argv, options);
/* 解析命令行選項并打開所有的輸入/輸出文件 */
ret = ffmpeg_parse_options(argc, argv);
if (ret < 0)
exit_program(1);
/* 檢查是否沒有指定輸出文件并且沒有輸入文件 */
if (nb_output_files <= 0 && nb_input_files == 0) {
show_usage();
av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
exit_program(1);
}
/* 檢查是否至少指定一個輸出文件 */
if (nb_output_files <= 0) {
av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
exit_program(1);
}
current_time = ti = get_benchmark_time_stamps();
/* 文件轉(zhuǎn)碼或抓取 */
if (transcode() < 0)
exit_program(1);
if (do_benchmark) {
int64_t utime, stime, rtime;
current_time = get_benchmark_time_stamps();
utime = current_time.user_usec - ti.user_usec;
stime = current_time.sys_usec - ti.sys_usec;
rtime = current_time.real_usec - ti.real_usec;
av_log(NULL, AV_LOG_INFO,
"bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",
utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);
}
av_log(NULL, AV_LOG_DEBUG,
"%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
decode_error_stat[0], decode_error_stat[1]);
/* 檢查解碼錯誤是否超過了指定的錯誤率 */
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69);
/* 根據(jù)是否接收到信號確定程序的返回碼,并終止程序 */
exit_program(received_nb_signals ? 255 : main_return_code);
return main_return_code;
}
下面是對每個步驟的功能的詳細解釋:
- 初始化動態(tài)加載。
- 調(diào)用init_dynload函數(shù),用于初始化動態(tài)加載庫的相關(guān)資源,以便在需要時加載需要的庫。
- 注冊退出回調(diào)函數(shù)。
- 調(diào)用register_exit函數(shù),將ffmpeg_cleanup函數(shù)注冊為在程序退出時被調(diào)用的回調(diào)函數(shù)。
- 設(shè)置stderr的緩沖模式。
- 調(diào)用setvbuf函數(shù),將stderr的緩沖模式設(shè)置為無緩沖模式,以確保錯誤信息可以立即顯示在終端上。
- 設(shè)置日志打印選項。
- 調(diào)用av_log_set_flags函數(shù),設(shè)置日志打印選項。在這里設(shè)置
AV_LOG_SKIP_REPEATED選項,表示日志會跳過重復的消息。
- 調(diào)用av_log_set_flags函數(shù),設(shè)置日志打印選項。在這里設(shè)置
- 解析命令行參數(shù)中的日志級別選項。
- 調(diào)用parse_loglevel函數(shù),解析命令行參數(shù)中的日志級別選項,并將其應用到日志系統(tǒng)中。
- 注冊音視頻設(shè)備。
- 調(diào)用avdevice_register_all函數(shù),用于注冊所有的音視頻設(shè)備。
- 初始化網(wǎng)絡(luò)模塊。
- 調(diào)用avformat_network_init函數(shù),初始化網(wǎng)絡(luò)模塊,以便進行網(wǎng)絡(luò)相關(guān)的操作,如打開網(wǎng)絡(luò)流。
- 顯示ffmpeg的banner信息。
- 調(diào)用show_banner函數(shù),根據(jù)命令行參數(shù)、選項和程序信息,打印ffmpeg的banner信息。
- 解析命令行選項并打開所有的輸入/輸出文件。
- 調(diào)用ffmpeg_parse_options函數(shù),解析命令行選項,并根據(jù)選項打開所有的輸入/輸出文件。
- 檢查是否沒有指定輸出文件并且沒有輸入文件。
- 檢查nb_output_files和nb_input_files的值。
- 若滿足條件,則打印用法信息和警告,并終止程序。
- 檢查是否至少指定一個輸出文件。
- 檢查nb_output_files的值。
- 若不滿足條件,則打印致命錯誤信息,并終止程序。
- 進行文件轉(zhuǎn)碼或抓取。
- 調(diào)用transcode函數(shù),進行文件轉(zhuǎn)碼或抓取。
- transcode函數(shù)返回值小于0表示出錯,并通過調(diào)用exit_program函數(shù)終止程序。
- 如果啟用了性能評測,輸出性能數(shù)據(jù)。
- 如果do_benchmark為真,計算從開始到結(jié)束的用戶時間、系統(tǒng)時間和真實時間,并打印出來。
- 打印解碼幀數(shù)和解碼錯誤數(shù)。
- 調(diào)用av_log函數(shù),打印成功解碼的幀數(shù)和解碼錯誤數(shù)。
- 檢查解碼錯誤是否超過了指定的錯誤率。
- 檢查解碼錯誤數(shù)是否超過了最大錯誤率。
- 若滿足條件,則通過調(diào)用exit_program終止程序。
- 根據(jù)是否接收到信號確定程序的返回碼,并終止程序。
- 返回main_return_code作為main函數(shù)的返回值。
針對main函數(shù)中幾個重要的函數(shù),下面將逐步解析他們的具體實現(xiàn)
ffmpeg_parse_options()
// 解析命令行參數(shù)并設(shè)置選項
int ffmpeg_parse_options(int argc, char **argv)
{
// 定義選項解析上下文和錯誤信息
OptionParseContext octx;
uint8_t error[128];
int ret;
memset(&octx, 0, sizeof(octx));
/* split the commandline into an internal representation */
// 將命令行參數(shù)拆分為內(nèi)部表示形式
ret = split_commandline(&octx, argc, argv, options, groups,
FF_ARRAY_ELEMS(groups));
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error splitting the argument list: ");
goto fail;
}
/* apply global options */
// 應用全局選項
ret = parse_optgroup(NULL, &octx.global_opts);
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error parsing global options: ");
goto fail;
}
/* configure terminal and setup signal handlers */
// 配置終端并設(shè)置信號處理程序
term_init();
/* open input files */
// 打開輸入文件
ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error opening input files: ");
goto fail;
}
// 應用同步偏移量
apply_sync_offsets();
/* create the complex filtergraphs */
// 創(chuàng)建復雜的濾波器圖
ret = init_complex_filters();
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n");
goto fail;
}
/* open output files */
// 打開輸出文件
ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error opening output files: ");
goto fail;
}
// 檢查濾波器的輸出
check_filter_outputs();
fail:
// 反初始化選項解析上下文
uninit_parse_context(&octx);
if (ret < 0) {
// 如果有錯誤,將錯誤信息輸出到日志
av_strerror(ret, error, sizeof(error));
av_log(NULL, AV_LOG_FATAL, "%s\n", error);
}
return ret;
}
函數(shù)功能:該函數(shù)用于解析命令行參數(shù)并進行相應處理,包括拆分命令行參數(shù)為內(nèi)部表示形式、應用全局選項、配置終端和設(shè)置信號處理程序、打開輸入文件、應用同步偏移量、創(chuàng)建復雜的濾波器圖、打開輸出文件、檢查濾波器的輸出。如果出現(xiàn)錯誤,會將錯誤信息輸出到日志。文章來源:http://www.zghlxwxcb.cn/news/detail-605499.html
transcode()
static int transcode(void)
{
int ret, i;
OutputStream *ost;
InputStream *ist;
int64_t timer_start;
int64_t total_packets_written = 0;
//執(zhí)行轉(zhuǎn)碼初始化操作,并將返回值賦給變量ret。
ret = transcode_init();
if (ret < 0)
goto fail;
//檢查是否啟用了從標準輸入進行交互的選項。
if (stdin_interaction) {
av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");
}
// 記錄轉(zhuǎn)碼開始的時間,用于計算整個過程的耗時。
timer_start = av_gettime_relative();
//執(zhí)行輸入線程的初始化,啟動輸入線程來讀取輸入文件。
if ((ret = init_input_threads()) < 0)
goto fail;
//持續(xù)循環(huán)進行轉(zhuǎn)碼操作,直到接收到停止信號。
while (!received_sigterm) {
int64_t cur_time= av_gettime_relative(); //記錄當前時間,用于計算轉(zhuǎn)碼過程中的耗時。
/* if 'q' pressed, exits */
/* 檢查是否啟用了從標準輸入進行交互的選項。如果啟用了與用戶交互的選項,
將會檢查是否需要檢查鍵盤交互來停止轉(zhuǎn)碼。*/
if (stdin_interaction)
if (check_keyboard_interaction(cur_time) < 0)
break;
/* check if there's any stream where output is still needed */
// 檢查是否還需要寫入輸出流
if (!need_output()) {
av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
break;
}
// 執(zhí)行轉(zhuǎn)碼的一步操作,比如從輸入文件中讀取數(shù)據(jù),應用濾鏡,寫入輸出文件等等。
ret = transcode_step();
if (ret < 0 && ret != AVERROR_EOF) {
av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));
break;
}
/* dump report by using the output first video and audio streams */
// 根據(jù)輸出流的第一個視頻和音頻流打印轉(zhuǎn)碼報告。顯示轉(zhuǎn)碼的進度和統(tǒng)計信息。
print_report(0, timer_start, cur_time);
}
free_input_threads(); //釋放輸入線程的資源。
/* at the end of stream, we must flush the decoder buffers */
//循環(huán)處理每個輸入流的解碼器緩沖區(qū),以確保在流結(jié)束時進行刷新。
for (i = 0; i < nb_input_streams; i++) {
ist = input_streams[i];
if (!input_files[ist->file_index]->eof_reached) {
process_input_packet(ist, NULL, 0);
}
}
flush_encoders(); // 刷新編碼器的緩沖區(qū),將剩余的數(shù)據(jù)編碼為輸出幀。
term_exit(); // 執(zhí)行終結(jié)操作,例如釋放資源和關(guān)閉設(shè)備。
/* write the trailer if needed */
// 循環(huán)寫入每個輸出文件的尾部。
for (i = 0; i < nb_output_files; i++) {
ret = of_write_trailer(output_files[i]);
if (ret < 0 && exit_on_error)
exit_program(1);
}
/* dump report by using the first video and audio streams */
// 根據(jù)第一個視頻和音頻流打印轉(zhuǎn)碼報告,顯示轉(zhuǎn)碼的最終進度和統(tǒng)計信息。
print_report(1, timer_start, av_gettime_relative());
/* close each encoder */
// 循環(huán)關(guān)閉每個輸出流的編碼器。
for (i = 0; i < nb_output_streams; i++) {
uint64_t packets_written;
ost = output_streams[i];
packets_written = atomic_load(&ost->packets_written);
total_packets_written += packets_written;
if (!packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) {
av_log(NULL, AV_LOG_FATAL, "Empty output on stream %d.\n", i);
exit_program(1);
}
}
// 如果輸出為空且設(shè)置了對空輸出流終止轉(zhuǎn)碼的標志,將會退出程序。
if (!total_packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT)) {
av_log(NULL, AV_LOG_FATAL, "Empty output\n");
exit_program(1);
}
/* close each decoder */
// 循環(huán)關(guān)閉每個輸入流的解碼器。
for (i = 0; i < nb_input_streams; i++) {
ist = input_streams[i];
if (ist->decoding_needed) {
avcodec_close(ist->dec_ctx);
}
}
// 釋放所有的硬件設(shè)備。
hw_device_free_all();
/* finished ! */
ret = 0;
fail:
free_input_threads(); // 釋放輸入線程所占用的資源。
// 如果存在輸出流,將會釋放輸出流相關(guān)的資源。
if (output_streams) {
for (i = 0; i < nb_output_streams; i++) {
ost = output_streams[i];
if (ost) {
if (ost->logfile) {
if (fclose(ost->logfile))
av_log(NULL, AV_LOG_ERROR,
"Error closing logfile, loss of information possible: %s\n",
av_err2str(AVERROR(errno)));
ost->logfile = NULL;
}
av_freep(&ost->forced_kf_pts);
av_freep(&ost->apad);
av_freep(&ost->disposition);
av_dict_free(&ost->encoder_opts);
av_dict_free(&ost->sws_dict);
av_dict_free(&ost->swr_opts);
}
}
}
return ret;
}
transcode()函數(shù)中每個流程的詳細操作步驟及功能,并與上下文的關(guān)系的說明:
- 初始化轉(zhuǎn)碼:調(diào)用transcode_init函數(shù)初始化轉(zhuǎn)碼過程。進行一些初始化操作,包括打開輸入和輸出文件,初始化輸入和輸出流等。該步驟在整個流程中只進行一次。
- 控制臺交互提示:如果啟用stdin_interaction參數(shù),打印控制臺交互提示信息。在控制臺輸出交互提示信息,指示用戶如何操作。根據(jù)參數(shù)stdin_interaction的設(shè)置,判斷是否需要進行控制臺交互提示。
- 初始化輸入線程:調(diào)用init_input_threads函數(shù)初始化輸入線程,準備進行輸入數(shù)據(jù)的讀取和處理。在轉(zhuǎn)碼過程中,可能需要使用多線程來進行輸入數(shù)據(jù)的讀取和處理。調(diào)用此函數(shù)來初始化輸入線程。
- 循環(huán)處理輸入數(shù)據(jù):在循環(huán)處理輸入數(shù)據(jù)的過程中,調(diào)用transcode_step函數(shù)來處理輸入數(shù)據(jù)。該函數(shù)通過選擇輸出流、處理濾鏡、處理輸入流等步驟,將輸入數(shù)據(jù)進行轉(zhuǎn)碼處理,并寫入到輸出流中。
- 打印報告:在轉(zhuǎn)碼過程中,定期調(diào)用print_report函數(shù)來打印轉(zhuǎn)碼報告,展示轉(zhuǎn)碼的進度和所消耗的時間。
- 釋放輸入線程::調(diào)用free_input_threads函數(shù)釋放輸入線程。在轉(zhuǎn)碼結(jié)束后,釋放輸入線程的資源,表示輸入數(shù)據(jù)的讀取和處理已經(jīng)完成。
- 刷新編碼器:調(diào)用flush_encoders函數(shù)刷新編碼器。在所有的輸入數(shù)據(jù)處理完畢后,需要將編碼器中剩余的數(shù)據(jù)進行編碼和寫入輸出流。調(diào)用flush_encoders函數(shù)來完成此操作。
- 退出程序:在轉(zhuǎn)碼流程結(jié)束后,進行一些資源的釋放和清理工作,然后調(diào)用term_exit函數(shù)退出程序。
- 寫入輸出文件尾:在轉(zhuǎn)碼結(jié)束后,需要將輸出文件的文件尾寫入。調(diào)用of_write_trailer函數(shù)完成此操作。
- 打印最終報告:在轉(zhuǎn)碼結(jié)束后,調(diào)用print_report函數(shù)來打印最終的轉(zhuǎn)碼報告,展示轉(zhuǎn)碼的總體進度和所消耗的時間。
- 關(guān)閉編碼器和解碼器:在轉(zhuǎn)碼結(jié)束后,需要關(guān)閉每個輸出流中的編碼器,以及每個輸入流中的解碼器,釋放相應的資源。
通過以上的流程,transcode()函數(shù)實現(xiàn)了將輸入數(shù)據(jù)進行轉(zhuǎn)碼處理,并將結(jié)果寫入輸出流中,最終完成轉(zhuǎn)碼操作。每個流程均有特定的功能,按照一定的順序進行處理,達到轉(zhuǎn)碼的目的。每個流程的操作都與上下文關(guān)系密切,根據(jù)上一個流程的處理結(jié)果和狀態(tài)來決定下一個流程的操作和流程邏輯。整個轉(zhuǎn)碼過程在循環(huán)中進行,直到所有的輸入數(shù)據(jù)處理完畢、輸出流刷新完畢,并將輸出文件的文件尾寫入,最后退出程序。
文章來源地址http://www.zghlxwxcb.cn/news/detail-605499.html
transcode_init()
static int transcode_init(void)
{
int ret = 0, i, j, k;
OutputStream *ost;
InputStream *ist;
char error[1024] = {0};
// 遍歷 nb_filtergraphs 列表中的 FilterGraph 結(jié)構(gòu)體。
for (i = 0; i < nb_filtergraphs; i++) {
FilterGraph *fg = filtergraphs[i];
for (j = 0; j < fg->nb_outputs; j++) {
OutputFilter *ofilter = fg->outputs[j];
if (!ofilter->ost || ofilter->ost->source_index >= 0) // 如果輸出的 ost 為空或 source_index 大于等于0,則跳過
continue;
if (fg->nb_inputs != 1) // 如果 FilterGraph 的輸入數(shù)不等于1,則跳過。
continue;
for (k = nb_input_streams-1; k >= 0 ; k--) // 從后往前遍歷輸入流列表,找到與 FilterGraph 的第一個輸入匹配的輸入流。
if (fg->inputs[0]->ist == input_streams[k])
break;
ofilter->ost->source_index = k; // 將找到的輸入流的索引賦值給 ost 的 source_index 屬性
}
}
/* init framerate emulation */
// 初始化幀率模擬
for (i = 0; i < nb_input_files; i++) { // 遍歷輸入文件列表。
InputFile *ifile = input_files[i];
if (ifile->readrate || ifile->rate_emu) // 如果輸入文件的 readrate 或 rate_emu 不為0,則對其所有流進行幀率模擬的初始化。
for (j = 0; j < ifile->nb_streams; j++)
input_streams[j + ifile->ist_index]->start = av_gettime_relative(); // 初始化方法是設(shè)置輸入流的 start 屬性為當前系統(tǒng)時間。
}
/* init input streams */
// 初始化輸入流
for (i = 0; i < nb_input_streams; i++) // 遍歷輸入流列表。
if ((ret = init_input_stream(i, error, sizeof(error))) < 0) // 調(diào)用 init_input_stream() 對輸入流進行初始化。
goto dump_format;
/*
* initialize stream copy and subtitle/data streams.
* Encoded AVFrame based streams will get initialized as follows:
* - when the first AVFrame is received in do_video_out
* - just before the first AVFrame is received in either transcode_step
* or reap_filters due to us requiring the filter chain buffer sink
* to be configured with the correct audio frame size, which is only
* known after the encoder is initialized.
*/
//初始化流復制和字幕/數(shù)據(jù)流:
for (i = 0; i < nb_output_streams; i++) { // 遍歷輸出流列表。
// 對于每個輸出流,檢查其是否為編碼的 AVFrame 流。如果是,則跳過初始化。
if (output_streams[i]->enc_ctx &&
(output_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
output_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
continue;
ret = init_output_stream_wrapper(output_streams[i], NULL, 0); // 初始化輸出流。
if (ret < 0)
goto dump_format;
}
/* discard unused programs */
// 丟棄未使用的程序:
for (i = 0; i < nb_input_files; i++) { // 遍歷輸入文件列表。
InputFile *ifile = input_files[i];
for (j = 0; j < ifile->ctx->nb_programs; j++) { // 對于每個輸入文件,遍歷其所有程序。
AVProgram *p = ifile->ctx->programs[j];
int discard = AVDISCARD_ALL;
// 檢查每個程序關(guān)聯(lián)的流是否需要丟棄。
for (k = 0; k < p->nb_stream_indexes; k++)
if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) {
discard = AVDISCARD_DEFAULT; // 如果有未被丟棄的流,則將程序的丟棄標志設(shè)置為默認丟棄。
break;
}
p->discard = discard;
}
}
dump_format:
/* dump the stream mapping */
// 打印流映射信息:
av_log(NULL, AV_LOG_INFO, "Stream mapping:\n");
for (i = 0; i < nb_input_streams; i++) {
ist = input_streams[i];
for (j = 0; j < ist->nb_filters; j++) {
// 對于每個輸入流的濾鏡圖,如果不是簡單濾鏡圖,則打印其相關(guān)信息。
if (!filtergraph_is_simple(ist->filters[j]->graph)) {
av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s",
ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?",
ist->filters[j]->name);
if (nb_filtergraphs > 1)
av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index);
av_log(NULL, AV_LOG_INFO, "\n");
}
}
}
// 打印輸出流映射信息
for (i = 0; i < nb_output_streams; i++) { // 遍歷輸出流列表。
ost = output_streams[i];
// 如果輸出流是附加的文件流,則打印其文件名和關(guān)聯(lián)的流信息。
if (ost->attachment_filename) {
/* an attached file */
av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n",
ost->attachment_filename, ost->file_index, ost->index);
continue;
}
// 如果輸出流是來自復雜濾鏡圖的輸出流,則打印濾鏡圖和關(guān)聯(lián)的流信息。
if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) {
/* output from a complex graph */
av_log(NULL, AV_LOG_INFO, " %s", ost->filter->name);
if (nb_filtergraphs > 1)
av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index);
av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index,
ost->index, ost->enc ? ost->enc->name : "?");
continue;
}
// 否則,打印輸入流和輸出流的對應關(guān)系。
av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d",
input_streams[ost->source_index]->file_index,
input_streams[ost->source_index]->st->index,
ost->file_index,
ost->index);
if (ost->enc_ctx) {
const AVCodec *in_codec = input_streams[ost->source_index]->dec;
const AVCodec *out_codec = ost->enc;
const char *decoder_name = "?";
const char *in_codec_name = "?";
const char *encoder_name = "?";
const char *out_codec_name = "?";
const AVCodecDescriptor *desc;
if (in_codec) {
decoder_name = in_codec->name;
desc = avcodec_descriptor_get(in_codec->id);
if (desc)
in_codec_name = desc->name;
if (!strcmp(decoder_name, in_codec_name))
decoder_name = "native";
}
if (out_codec) {
encoder_name = out_codec->name;
desc = avcodec_descriptor_get(out_codec->id);
if (desc)
out_codec_name = desc->name;
if (!strcmp(encoder_name, out_codec_name))
encoder_name = "native";
}
av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))",
in_codec_name, decoder_name,
out_codec_name, encoder_name);
} else
av_log(NULL, AV_LOG_INFO, " (copy)");
av_log(NULL, AV_LOG_INFO, "\n");
}
if (ret) {
av_log(NULL, AV_LOG_ERROR, "%s\n", error);
return ret;
}
atomic_store(&transcode_init_done, 1);
return 0;
}
該函數(shù)是一個用于初始化轉(zhuǎn)碼器的函數(shù)。下面是每個步驟的詳細操作步驟及功能,并與上下文的關(guān)系進行說明:
- 初始化source_index:該步驟的目的是為每個輸出流設(shè)置源輸入流的索引(source_index)。這個索引用于指示輸出流的源是哪個輸入流。根據(jù)轉(zhuǎn)碼器的邏輯,只有當輸出流沒有源或者它的源索引為負值時,才需要設(shè)置源索引。如果過濾圖的輸入數(shù)目不為1,則跳過該輸出流。然后,遍歷所有的輸入流,找到與當前過濾圖的輸入對應的輸入流索引并將其設(shè)置為源索引。
- 初始化幀率仿真:這一步驟的目的是為了在處理視頻幀時模擬輸入文件的特定幀速率。對于每個輸入文件,如果設(shè)置了readrate或rate_emu參數(shù),則遍歷該文件的每個流,設(shè)置其開始時間為相對于系統(tǒng)啟動時間的時間戳。
- 初始化輸入流:對于每個輸入流,調(diào)用init_input_stream函數(shù)進行初始化。如果初始化失敗,則跳轉(zhuǎn)到dump_format標簽,進行格式轉(zhuǎn)換。
- 初始化流復制和字幕/數(shù)據(jù)流:這一步驟的目的是初始化流的復制、字幕和數(shù)據(jù)流。對于每個輸出流,如果其對應的編碼上下文存在且流的類型是視頻或音頻,則跳過該輸出流。否則,調(diào)用init_output_stream_wrapper函數(shù)進行初始化。
- 丟棄未使用的節(jié)目:這一步驟的目的是丟棄未使用的節(jié)目,即那些沒有被任何輸入流參考的節(jié)目。對于每個輸入文件,遍歷其所有的節(jié)目,并檢查每個節(jié)目中的流索引。如果找到至少一個流不是丟棄的,則將該節(jié)目的discard設(shè)置為AVDISCARD_DEFAULT,否則設(shè)置為AVDISCARD_ALL。
- 打印流映射:輸出日志以打印流映射關(guān)系。依次遍歷每個輸入流,并對于每個輸入流中的濾鏡依次輸出相關(guān)信息。這一步驟的作用是在日志中顯示輸入流的濾鏡鏈。
- 打印輸出流映射:輸出日志以打印輸出流映射關(guān)系。依次遍歷每個輸出流,并輸出與該輸出流相關(guān)的信息,如附加文件信息、復雜圖結(jié)構(gòu)信息等。
- 檢查初始化結(jié)果:如果初始化過程中出現(xiàn)錯誤,則返回錯誤碼并輸出錯誤信息。否則,設(shè)置transcode_init_done標志為1,表示轉(zhuǎn)碼器已經(jīng)初始化完成,并返回0表示成功初始化。
transcode_step()
static int transcode_step(void)
{
OutputStream *ost; // 輸出流指針
InputStream *ist = NULL; // 輸入流指針
int ret;
// 選擇輸出流
ost = choose_output();
if (!ost) {
// 如果沒有輸出流
if (got_eagain()) { // 檢查是否發(fā)生 EAGAIN 錯誤
reset_eagain();
av_usleep(10000); // 延時等待一段時間
return 0;
}
av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n");
return AVERROR_EOF;
}
if (ost->filter && !ost->filter->graph->graph) {
// 如果輸出流有濾鏡,并且濾鏡圖沒有構(gòu)建,則進行濾鏡圖的配置
if (ifilter_has_all_input_formats(ost->filter->graph)) {
ret = configure_filtergraph(ost->filter->graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n");
return ret;
}
}
}
if (ost->filter && ost->filter->graph->graph) {
// 如果輸出流有濾鏡,并且濾鏡圖已經(jīng)構(gòu)建,則進行相關(guān)操作
if (av_buffersink_get_type(ost->filter->filter) == AVMEDIA_TYPE_AUDIO)
init_output_stream_wrapper(ost, NULL, 1); // 音頻特殊處理,進行輸出流的初始化
ret = transcode_from_filter(ost->filter->graph, &ist); // 從濾鏡開始轉(zhuǎn)碼
if (ret < 0)
return ret;
if (!ist)
return 0;
} else if (ost->filter) {
// 如果輸出流有濾鏡,但濾鏡圖沒有構(gòu)建,則嘗試找到輸入流
int i;
for (i = 0; i < ost->filter->graph->nb_inputs; i++) {
InputFilter *ifilter = ost->filter->graph->inputs[i];
if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) {
ist = ifilter->ist;
break;
}
}
if (!ist) {
ost->inputs_done = 1; // 輸入流處理完畢
return 0;
}
} else {
// 如果輸出流沒有濾鏡,則直接使用輸入流
av_assert0(ost->source_index >= 0);
ist = input_streams[ost->source_index];
}
ret = process_input(ist->file_index); // 處理輸入
if (ret == AVERROR(EAGAIN)) {
// 如果返回 EAGAIN 錯誤
if (input_files[ist->file_index]->eagain)
ost->unavailable = 1;
return 0;
}
if (ret < 0)
return ret == AVERROR_EOF ? 0 : ret;
return reap_filters(0); // 回收濾鏡
}
transcode_step函數(shù)實現(xiàn)了對輸入流的處理和轉(zhuǎn)碼操作。具體的處理方式和操作根據(jù)輸出流的關(guān)聯(lián)情況和濾鏡圖的配置狀態(tài)進行調(diào)整,以達到轉(zhuǎn)碼的功能。
- 選擇輸出流:調(diào)用choose_output函數(shù)選擇一個輸出流進行處理。如果沒有可用的輸出流,則檢查是否收到EAGAIN錯誤,如果是則等待一段時間后返回0,否則輸出日志并返回AVERROR_EOF表示已經(jīng)沒有更多的輸入需要讀取。
- 配置濾鏡圖:對輸出流關(guān)聯(lián)的濾鏡圖進行配置。如果輸出流關(guān)聯(lián)了濾鏡圖,并且濾鏡圖尚未被配置,則調(diào)用configure_filtergraph函數(shù)對濾鏡圖進行配置。
- 處理帶濾鏡的輸出流:對關(guān)聯(lián)了濾鏡圖的輸出流進行處理。如果輸出流關(guān)聯(lián)了濾鏡圖,并且濾鏡圖已經(jīng)被配置,就進行處理。這個步驟包含了對特殊情況的處理,主要是針對音頻的初始化。然后調(diào)用transcode_from_filter函數(shù)對濾鏡圖進行處理,并通過傳入的指針返回輸入流。
- 處理無濾鏡的輸出流:對沒有關(guān)聯(lián)濾鏡圖的輸出流進行處理。如果輸出流沒有關(guān)聯(lián)濾鏡圖,但關(guān)聯(lián)了輸出流的源輸入流,則遍歷濾鏡圖的輸入,找到第一個沒有輸出的輸入流,并將其賦值給ist指針。如果所有的輸入流都已經(jīng)輸出完畢(eof_reached標志為真),則將輸出流的inputs_done標志置為1表示輸入流處理完畢,并返回0。
- 處理輸入流:調(diào)用process_input函數(shù)處理輸入流。具體操作是讀取輸入幀,解碼并進行相應的處理。返回值可能為EAGAIN表示需要更多的輸入,所以直接返回0。
- 處理輸入錯誤:如果處理輸入流發(fā)生錯誤,則根據(jù)錯誤碼判斷是否為AVERROR_EOF,如果是則返回0表示輸入流處理完畢,否則返回錯誤碼。
- 回收濾鏡:調(diào)用reap_filters函數(shù)回收濾鏡。這個函數(shù)會檢查濾鏡圖中的每個濾鏡的輸出是否準備好,如果準備好則進行相關(guān)操作。返回0表示濾鏡回收完畢,繼續(xù)進行下一輪的處理。
,
到了這里,關(guān)于FFMPEG源碼之ffmpeg.c解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!