使用する共通APIとC言語の開発:FFmpegのは、チュートリアルをはじめ
ffmpegのと接触しているプロジェクトの使用のための理由なので、命令によるトランスコードビデオを、ffmpegの呼び出すためにC#を使用しました。どのように指示使いやすいが、それは、オーディオとビデオのより複雑な二次開発に来る場合、オーディオとビデオの関連する概念のない一定の知識がない場合、それはハードコードの意味やロジックを理解するために見つけます。以来関心は最近、適切な学習のffmpegのAPIの使用を検討し始めています。
理解概念
1、マルチメディアファイルの基本的な考え方
- これは、マルチメディアコンテナファイルであります
- コンテナ内の多くのストリーム(ストリーム/トラック)があります。
- 各ストリームは異なる符号器によって符号化されます
- データが呼ばれるパケットストリームから読み出し
- これは、1つのパケット内の1つまたは複数のフレームを含みます
図2に示すように、符号化されたオーディオの量子化
- 信号(連続のアナログからデジタルへの変換 - >離散的、非連続的プロセスは、コンピュータによって使用されます
- アナログ - >サンプル - >定量 - >コーディング - >デジタル・シグナル
- サンプルサイズ:ビット記憶の数のサンプル、一般的に使用される16ビットの基本的な概念を定量化
- サンプリングレート:であり、サンプリング周波数(サンプリング周波数1秒)、一般的なサンプルレート8kHzの、16kHzの、32kHzの、44.1kHzの、48kHzの、等、より高いサンプリング周波数、コースのより現実的な音より自然な減少、より大きなデータ量
- チャネル数:音場を再生するとき、真の音を復元することができるようにするために、同時に音が音を記録しながら、周りのいくつかの異なる方向に取得するには、各方向の音はチャネルです。スピーカーまたはオーディオ録音の数に応じた数のチャネルの数が再生されている、モノ、2チャネル、マルチチャネルがあります
- レート:また、ビットレートとして知られているが、毎秒送信されたビットの数です。BPS(ビット毎秒)の単位、より高いビットレート、毎秒以上のデータ転送、より良い音質。
码率计算公式:
码率 = 采样率 * 采样大小 * 声道数
比如采样率44.1kHz,采样大小为16bit,双声道PCM编码的WAV文件:
码率=44.1hHz*16bit*2=1411.2kbit/s。
录制1分钟的音乐的大小为(1411.2 * 1000 * 60) / 8 / 1024 / 1024 = 10.09M。
図3に示すように、時間イル
- time_baseの時間、例えばtime_baseを測定するために使用され= {1,40}は、それが1秒の40個のセグメントに分割されることを意味し、各セグメントは、FFmpegのがav_q2d関数に、1/40秒(time_baseは)の期間を計算するために使用されます時間は、演算結果が1/40秒です。例えば、ビデオPTSあるフレームは、最初の20秒と言うことであり、それは何秒も示し800個のセグメントが存在することを意味800、PTS av_q2d(time_base)= 800(1/40)= 20S、あります演奏は、フレームのタイムベースを変換するとき。異なるフォーマットの異なるタイムベース。
- PTSは、レンダリングとタイムスタンプです。DTSは、デコードタイムスタンプです。
オーディオPTS:例とAACオーディオ、AACフレームは、44.1kHzのサンプリングレート(1秒の取得44,100サンプル)と、1024個のサンプルを言うことで、元のデータ及び時間の期間の1024個のサンプルを含み、 AAC 44100/1024オーディオフレームが1秒であり、各フレームの持続時間は、それによって、PTSをフレーム毎に算出することができる44100分の1024秒です。 - 変換式
timestamp(秒) = pts * av_q2d(st->time_base)//计算该帧在视频音频中的位置
time(秒) = st->duration * av_q2d(st->time_base)//计算视频音频中的长度
st 为AVStream流指针
时间基转换公式
timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)//timestamp就算是PTS/DTS
環境設定
ダウンロード
入力の公式ウェブサイトは、デベロッパーと共有アーカイブをダウンロードしました。オプションは、対応するプラットフォームをダウンロードすることに注意してください。
devが、含まれ、LIBファイルは以下のディレクトリに抽出されます。Debugディレクトリ、サブエラーが発生します共有プロジェクトにdllファイルをコピーします。
環境設定
VSで作成するC / C ++プロジェクト、財産権プロジェクト
のdllファイル、次の追加するには
avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib
libavcodec 提供一系列编码器的实现
libavformat 实现在流协议,容器格式及其IO访问
libavutil 包括了hash器、解码器和各种工具函数
libavfilter 提供了各种音视频过滤器
libavdevice 提供了访问捕获设备和回放设备的接口
libswresample 实现了混音和重采样
libswscale 实现了色彩转换和缩放功能
テスト
私は開発のためのエディタとしてVS2017を使用しています。
#include<stdio.h>
#include <iostream>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
int main(int argc, char* argv[]) {
printf(avcodec_configuration());
system("pause");
return 0;
}
開発事例
ビデオとオーディオのミックスと2本のビデオを達成するために、一致、少しコーヒーが同様の特徴を示します。
そして、APIを使用するためのロジックを処理
- APIの登録
- 入力、出力コンテキストを作成します。
- 入力音声ストリームを取得し、入力ビデオストリーム
- 出力オーディオストリーム、ビデオストリーム出力を作成します
- 入力パラメータは、出力ストリームのフローパラメータにコピーされ
- ファイルサイズを確認し、ファイルの長さの出力が決定しました
- 書き込みヘッダ
- 初期化パケット、オーディオとビデオのデータが読み込まれ、ファイルに書き込まれます
関連するコード
#include<stdio.h>
#include <iostream>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
}
#define ERROR_STR_SIZE 1024
int main(int argc, char const *argv[])
{
int ret = -1;
int err_code;
char errors[ERROR_STR_SIZE];
AVFormatContext *ifmt_ctx1 = NULL;
AVFormatContext *ifmt_ctx2 = NULL;
AVFormatContext *ofmt_ctx = NULL;
AVOutputFormat *ofmt = NULL;
AVStream *in_stream1 = NULL;
AVStream *in_stream2 = NULL;
AVStream *out_stream1 = NULL;
AVStream *out_stream2 = NULL;
int audio_stream_index = 0;
int vedio_stream_indes = 0;
// 文件最大时长,保证音频和视频数据长度一致
double max_duration = 0;
AVPacket pkt;
int stream1 = 0, stream2 = 0;
av_log_set_level(AV_LOG_DEBUG);
//打开两个输入文件
if ((err_code = avformat_open_input(&ifmt_ctx1, "C:\\Users\\haizhengzheng\\Desktop\\meta.mp4", 0, 0)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR, "Could not open src file, %s, %d(%s)\n",
"C:\\Users\\haizhengzheng\\Desktop\\meta.mp4", err_code, errors);
goto END;
}
if ((err_code = avformat_open_input(&ifmt_ctx2, "C:\\Users\\haizhengzheng\\Desktop\\mercury.mp4", 0, 0)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,
"Could not open the second src file, %s, %d(%s)\n",
"C:\\Users\\haizhengzheng\\Desktop\\mercury.mp4", err_code, errors);
goto END;
}
//创建输出上下文
if ((err_code = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4")) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR, "Failed to create an context of outfile , %d(%s) \n",
err_code, errors);
}
ofmt = ofmt_ctx->oformat;//获得输出文件的格式信息
// 找到第一个参数里最好的音频流和第二个文件中的视频流下标
audio_stream_index = av_find_best_stream(ifmt_ctx1, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);//获取音频流下标
vedio_stream_indes = av_find_best_stream(ifmt_ctx2, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);//获取视频流下标
// 获取第一个文件中的音频流
in_stream1 = ifmt_ctx1->streams[audio_stream_index];
stream1 = 0;
// 创建音频输出流
out_stream1 = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream1) {
av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
goto END;
}
// 拷贝流参数
if ((err_code = avcodec_parameters_copy(out_stream1->codecpar, in_stream1->codecpar)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,
"Failed to copy codec parameter, %d(%s)\n",
err_code, errors);
}
out_stream1->codecpar->codec_tag = 0;
// 获取第二个文件中的视频流
in_stream2 = ifmt_ctx2->streams[vedio_stream_indes];
stream2 = 1;
// 创建视频输出流
out_stream2 = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream2) {
av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
goto END;
}
// 拷贝流参数
if ((err_code = avcodec_parameters_copy(out_stream2->codecpar, in_stream2->codecpar)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,
"Failed to copy codec parameter, %d(%s)\n",
err_code, errors);
goto END;
}
out_stream2->codecpar->codec_tag = 0;
//输出流信息
av_dump_format(ofmt_ctx, 0, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", 1);
// 判断两个流的长度,确定最终文件的长度 time(秒) = st->duration * av_q2d(st->time_base) duration 就是dts\pts av_q2d()就是倒数
if (in_stream1->duration * av_q2d(in_stream1->time_base) > in_stream2->duration * av_q2d(in_stream2->time_base)) {
max_duration = in_stream2->duration * av_q2d(in_stream2->time_base);
}
else {
max_duration = in_stream1->duration * av_q2d(in_stream1->time_base);
}
//打开输出文件
if (!(ofmt->flags & AVFMT_NOFILE)) {
if ((err_code = avio_open(&ofmt_ctx->pb, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", AVIO_FLAG_WRITE)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,
"Could not open output file, %s, %d(%s)\n",
"C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", err_code, errors);
goto END;
}
}
//写头信息
avformat_write_header(ofmt_ctx, NULL);
av_init_packet(&pkt);
// 读取音频数据并写入输出文件中
while (av_read_frame(ifmt_ctx1, &pkt) >= 0) {
// 如果读取的时间超过了最长时间表示不需要该帧,跳过
if (pkt.pts * av_q2d(in_stream1->time_base) > max_duration) {
av_packet_unref(&pkt);
continue;
}
// 如果是我们需要的音频流,转换时间基后写入文件 av_rescale_q_rnd()时间基转换函数
if (pkt.stream_index == audio_stream_index) {
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream1->time_base, out_stream1->time_base,//获取包的PTS\DTS\duration
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream1->time_base, out_stream1->time_base,
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(max_duration, in_stream1->time_base, out_stream1->time_base);
pkt.pos = -1;
pkt.stream_index = stream1;
av_interleaved_write_frame(ofmt_ctx, &pkt);
av_packet_unref(&pkt);
}
}
// 读取视频数据并写入输出文件中
while (av_read_frame(ifmt_ctx2, &pkt) >= 0) {
// 如果读取的时间超过了最长时间表示不需要该帧,跳过
if (pkt.pts * av_q2d(in_stream2->time_base) > max_duration) {
av_packet_unref(&pkt);
continue;
}
// 如果是我们需要的视频流,转换时间基后写入文件
if (pkt.stream_index == vedio_stream_indes) {
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream2->time_base, out_stream2->time_base,
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream2->time_base, out_stream2->time_base,
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(max_duration, in_stream2->time_base, out_stream2->time_base);
pkt.pos = -1;
pkt.stream_index = stream2;
av_interleaved_write_frame(ofmt_ctx, &pkt);
av_packet_unref(&pkt);
}
}
//写尾信息
av_write_trailer(ofmt_ctx);
ret = 0;
END:
// 释放内存
if (ifmt_ctx1) {
avformat_close_input(&ifmt_ctx1);
}
if (ifmt_ctx2) {
avformat_close_input(&ifmt_ctx2);
}
if (ofmt_ctx) {
if (!(ofmt->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
}
avformat_free_context(ofmt_ctx);
}
}