FFmpeg 通過 showwavespic 獲取音訊的頻譜圖
FFmpeg 的 showwavespic 濾鏡如何得到頻譜圖
音訊資料通常由波形影象表示。
FFmpeg 通過使用 showwavespic 可以得到音訊資料的頻譜圖
ffmpeg -i input -filter_complex "showwavespic=s=640x120" -frames:v 1 output.png
執行上面一條命令之後,即可得到一張如下的圖片:
那麼 FFmpeg 是如何將音訊資料轉換為波形圖的呢?
首先通過命令我們知道使用了名為showwavespic
的濾鏡,根據名字大概猜想此濾鏡就是生成頻譜圖的關鍵所在。
所以,我麼直接定位到 showwavespic
// showwavespic 濾鏡的輸入
static const AVFilterPad showwavespic_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.config_props = showwavespic_config_input,
.filter_frame = showwavespic_filter_frame,
},
{ NULL }
};
// showwavespic 濾鏡的輸出
static const AVFilterPad showwavespic_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_output, // 配置下一個濾鏡的相關引數(例如輸出frame 的寬、高)
.request_frame = request_frame,
},
{ NULL }
};
AVFilter ff_avf_showwavespic = {
.name = "showwavespic", // 輸入的音訊轉換為頻譜圖輸出
.description = NULL_IF_CONFIG_SMALL("Convert input audio to a video output single picture."),
.init = init, // 初始化方法
.uninit = uninit,
.query_formats = query_formats, // 濾鏡支援的格式
.priv_size = sizeof(ShowWavesContext),
.inputs = showwavespic_inputs,
.outputs = showwavespic_outputs,
.priv_class = &showwavespic_class,
};
通過參考其他資源,理清楚濾鏡的工作流程。花費幾天的時間閱讀 FFmpeg 的原始碼,生成波形圖的原理 – 解碼音訊檔案得到音訊裸資料 —> 通過 showwavespic 濾鏡處理PCM資料得到波形圖
showwavespic 濾鏡是如何處理 PCM 資料得到波形圖的呢?
PCM 資料
首先我們要了解什麼是 PCM 音訊資料:
PCM(Pulse Code Modulation)稱為脈衝編碼調製,PCM 音訊資料是未經壓縮的音訊取樣資料裸流,它是由模擬訊號經過取樣、量化、編碼轉換成的標準的數字音訊資料。
儲存格式
如果是單聲道的音訊檔案,取樣資料按時間的先後順序依次存入(有時也會採用LRLRLR方式儲存,只是另一個聲道的資料為0),如果是雙聲道的話就按照LRLRLR的方式儲存。
單聲道
+------+------+------+------+------+------+------+------+------+
| 500 | 300 | -100 | -20 | -300 | 900 | -200 | -50 | 250 |
+------+------+------+------+------+------+------+------+------+
每個取樣的整數的大小最小為 -32768,最大為 32768。根據取樣資料的位置和值畫一個圖的話,就會得到像播放器上那樣的波浪形圖。
立體聲的取樣是每一個 frame 是一個 16bit 的取樣點。左右聲道的資料交叉存放。
那麼取樣資料的絕對值按照生成圖片的高的比例即可得出振幅。頻率通過生成圖片的寬計算得到。
- 音訊檔案解碼得到 PCM(音訊裸資料), 統計音訊的取樣總數
- 以 取樣總數 / 輸出圖片的寬度 為波形圖統計頻率
- 取樣資料的絕對值 * 生成圖片的高度 / 32768 計算得出振幅大小
-
濾鏡處理流程
-
流程詳情
-
init
– showwavespic 濾鏡的初始化static av_cold int init(AVFilterContext *ctx) { // showwaves 濾鏡的私有資料 ShowWavesContext *showwaves = ctx->priv; if (!strcmp(ctx->filter->name, "showwavespic")) { // 如果是 showwavespic 濾鏡 showwaves->single_pic = 1; // 使用 cline 的繪圖 mode showwaves->mode = MODE_CENTERED_LINE; } return 0; }
-
showwavespic_config_input
– 配置 showwavespic 相關屬性static int showwavespic_config_input(AVFilterLink *inlink) { // showwavespic 濾鏡 AVFilterContext *ctx = inlink->dst; // 濾鏡私有引數 ShowWavesContext *showwaves = ctx->priv; if (showwaves->single_pic) { // 聲道取樣資料的和(初始化陣列記憶體空間) showwaves->sum = av_mallocz_array(inlink->channels, sizeof(*showwaves->sum)); if (!showwaves->sum) return AVERROR(ENOMEM); } return 0; }
-
config_output
– 配置輸出影象的引數 & showwavespic 濾鏡引數static int config_output(AVFilterLink *outlink) { // 程式碼較長,省略 ... // 取樣的x、y座標 showwaves->buf_idx = 0; if (!(showwaves->buf_idy = av_mallocz_array(nb_channels, sizeof(*showwaves->buf_idy)))) { av_log(ctx, AV_LOG_ERROR, "Could not allocate showwaves buffer\n"); return AVERROR(ENOMEM); } // 輸出圖片的寬高、寬高比、幀率 outlink->w = showwaves->w; outlink->h = showwaves->h; outlink->sample_aspect_ratio = (AVRational){1,1}; // 1 outlink->frame_rate = av_div_q((AVRational){inlink->sample_rate,showwaves->n}, (AVRational){showwaves->w,1}); // 設定 draw_sample & get_h 函式 ... // 預設使用的顏色為: red|green|... colors = av_strdup(showwaves->colors); if (!colors) return AVERROR(ENOMEM); /* multiplication factor, pre-computed to avoid in-loop divisions */ x = 255 / ((showwaves->split_channels ? 1 : nb_channels) * showwaves->n); // 255/2 if (outlink->format == AV_PIX_FMT_RGBA) { uint8_t fg[4] = { 0xff, 0xff, 0xff, 0xff }; // 左聲道為紅色,右聲道為綠色 for (ch = 0; ch < nb_channels; ch++) { char *color; color = av_strtok(ch == 0 ? colors : NULL, " |", &saveptr); if (color) av_parse_color(fg, color, -1, ctx); showwaves->fg[4*ch + 0] = fg[0] * x / 255.; showwaves->fg[4*ch + 1] = fg[1] * x / 255.; showwaves->fg[4*ch + 2] = fg[2] * x / 255.; showwaves->fg[4*ch + 3] = fg[3] * x / 255.; } } else { for (ch = 0; ch < nb_channels; ch++) showwaves->fg[4 * ch + 0] = x; } av_free(colors); }
-
showwavespic_filter_frame
– 配置 showwavespic 濾鏡的引數(初始化輸出frame、音訊幀等)static int showwavespic_filter_frame(AVFilterLink *inlink, AVFrame *insamples) { // showwavespic 濾鏡 AVFilterContext *ctx = inlink->dst; // showwavespic 濾鏡與其下一個濾鏡之間的聯絡 AVFilterLink *outlink = ctx->outputs[0]; // showwavespic 濾鏡的私有資料 ShowWavesContext *showwaves = ctx->priv; // 輸入資料 int16_t *p = (int16_t *)insamples->data[0]; int ret = 0; if (showwaves->single_pic) { struct frame_node *f; // 給 showwaves 濾鏡的輸出圖片 frame 分配一個空的buffer ret = alloc_out_frame(showwaves, p, inlink, outlink, insamples); if (ret < 0) goto end; /* queue the audio frame (audio frame 佇列)*/ f = av_malloc(sizeof(*f)); if (!f) { ret = AVERROR(ENOMEM); goto end; } f->frame = insamples; f->next = NULL; // showwavespic 濾鏡的音訊佇列 if (!showwaves->last_frame) { showwaves->audio_frames = showwaves->last_frame = f; } else { showwaves->last_frame->next = f; showwaves->last_frame = f; } // 總音訊取樣數 showwaves->total_samples += insamples->nb_samples; return 0; } end: av_frame_free(&insamples); return ret; }
-
request_frame
– 請求濾鏡處理後的 framestatic int request_frame(AVFilterLink *outlink) { ShowWavesContext *showwaves = outlink->src->priv; AVFilterLink *inlink = outlink->src->inputs[0]; int ret; ret = ff_request_frame(inlink); if (ret == AVERROR_EOF && showwaves->outpicref) { // 讀取完所有的 frame if (showwaves->single_pic) push_single_pic(outlink); // 生成頻譜圖 else push_frame(outlink); } return ret; }
push_single_pic
– 根據取樣資料生成頻譜圖,並傳給下一個濾鏡static int push_single_pic(AVFilterLink *outlink) { // showwavespic 濾鏡 AVFilterContext *ctx = outlink->src; // showwavespic 與上一個濾鏡之間的聯絡 AVFilterLink *inlink = ctx->inputs[0]; // showwavespic 濾鏡的私有資料 ShowWavesContext *showwaves = ctx->priv; // max_samples -- 音訊總取樣數 / 輸出圖片的寬(頻率) int64_t n = 0, max_samples = showwaves->total_samples / outlink->w; // 輸出 frame AVFrame *out = showwaves->outpicref; struct frame_node *node; // 聲道數 const int nb_channels = inlink->channels; const int ch_height = showwaves->split_channels ? outlink->h / nb_channels : outlink->h; // h const int linesize = out->linesize[0]; const int pixstep = showwaves->pixstep; // 4 int col = 0; int64_t *sum = showwaves->sum; if (max_samples == 0) { av_log(ctx, AV_LOG_ERROR, "Too few samples\n"); return AVERROR(EINVAL); } av_log(ctx, AV_LOG_DEBUG, "Create frame averaging %"PRId64" samples per column\n", max_samples); memset(sum, 0, nb_channels); // 迴圈從濾鏡 audio 佇列中取出 frame for (node = showwaves->audio_frames; node; node = node->next) { int i; const AVFrame *frame = node->frame; // 當前 frame 的資料 const int16_t *p = (const int16_t *)frame->data[0]; // 當前 frame 的取樣數 for (i = 0; i < frame->nb_samples; i++) { int ch; for (ch = 0; ch < nb_channels; ch++) sum[ch] += abs(p[ch + i*nb_channels]) << 1; if (n++ == max_samples) { for (ch = 0; ch < nb_channels; ch++) { int16_t sample = sum[ch] / max_samples; uint8_t *buf = out->data[0] + col * pixstep; int h; if (showwaves->split_channels) buf += ch*ch_height*linesize; av_assert0(col < outlink->w); h = showwaves->get_h(sample, ch_height); showwaves->draw_sample(buf, ch_height, linesize, &showwaves->buf_idy[ch], &showwaves->fg[ch * 4], h); sum[ch] = 0; } col++; n = 0; } } } return push_frame(outlink); }
-