FFmpeg - передискретизация с AV_SAMPLE_FMT_FLTP на AV_SAMPLE_FMT_S16 дает очень плохое качество звука (медленный, расстроенный, шум)

Меня смутил результат передискретизации в новом ffmpeg. Я декодирую аудио AAC в PCM, ffmpeg показывает аудиоинформацию как:

Stream #0:0: Audio: aac, 44100 Hz, stereo, fltp, 122 kb/s

В новом ffmpeg выходные образцы имеют формат fltp, поэтому мне нужно преобразовать его из AV_SAMPLE_FMT_FLTP в AV_SAMPLE_FMT_S16

PS: в старом ffmpeg как libavcodec 54.12.100 это напрямую S16, поэтому не требуется передискретизация и никаких проблем с качеством звука.

Затем я попробовал три способа передискретизации:

  1. используя swr_convert

  2. используя avresample_convert

  3. конвертировать вручную

Но все они дают один и тот же результат, качество звука очень плохое, очень медленное и расстроенное, с небольшим шумом.

Мой код передискретизации выглядит следующим образом:

void resampling(AVFrame* frame_, AVCodecContext* pCodecCtx, int64_t want_sample_rate, uint8_t* outbuf){
    SwrContext      *swrCtx_ = 0;
    AVAudioResampleContext *avr = 0;

    // Initializing the sample rate convert. We only really use it to convert float output into int.
    int64_t wanted_channel_layout = AV_CH_LAYOUT_STEREO;

#ifdef AV_SAMPLEING
    avr = avresample_alloc_context();
    av_opt_set_int(avr, "in_channel_layout", frame_->channel_layout, 0);
    av_opt_set_int(avr, "out_channel_layout", wanted_channel_layout, 0);
    av_opt_set_int(avr, "in_sample_rate", frame_->sample_rate, 0);
    av_opt_set_int(avr, "out_sample_rate", 44100, 0);
    av_opt_set_int(avr, "in_sample_fmt", pCodecCtx->sample_fmt, 0); //AV_SAMPLE_FMT_FLTP
    av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
    av_opt_set_int(avr, "internal_sample_fmt", pCodecCtx->sample_fmt, 0);
    avresample_open(avr);
    avresample_convert(avr, &outbuf, frame_->linesize[0], frame_->nb_samples, frame_->extended_data, frame_->linesize[0], frame_->nb_samples);
    avresample_close(avr);
    return;
#endif

#ifdef USER_SAMPLEING
    if (pCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
    {
            int nb_samples = frame_->nb_samples;
            int channels = frame_->channels;
            int outputBufferLen = nb_samples & channels * 2;
            auto outputBuffer = (int16_t*)outbuf;

            for (int i = 0; i < nb_samples; i++)
            {
                    for (int c = 0; c < channels; c++)
                    {
                            float* extended_data = (float*)frame_->extended_data[c];
                            float sample = extended_data[i];
                            if (sample < -1.0f) sample = -1.0f;
                            else if (sample > 1.0f) sample = 1.0f;
                            outputBuffer[i * channels + c] = (int16_t)round(sample * 32767.0f);
                    }
            }
            return;
    }
#endif
    swrCtx_ = swr_alloc_set_opts(
            NULL, //swrCtx_,
            wanted_channel_layout,
            AV_SAMPLE_FMT_S16,
            want_sample_rate,
            pCodecCtx->channel_layout,
            pCodecCtx->sample_fmt,
            pCodecCtx->sample_rate,
            0,
            NULL);

    if (!swrCtx_ || swr_init(swrCtx_) < 0) {
            printf("swr_init: Failed to initialize the resampling context");
            return;
    }

    // convert audio to AV_SAMPLE_FMT_S16
    int swrRet = swr_convert(swrCtx_, &outbuf, frame_->nb_samples, (const uint8_t **)frame_->extended_data, frame_->nb_samples);
    if (swrRet < 0) {
            printf("swr_convert: Error while converting %d", swrRet);
            return;
    }
}

Что делать?

PS1: играть с ffplay - это нормально.

PS2: сохраните ресэмпл S16 PCM в файл, и при его воспроизведении будет такая же проблема с качеством звука.

Большое спасибо за вашу помощь и предложения!


Я также заметил, что в старом ffmpeg aac распознается как формат FLT и напрямую декодируется в 16-битный PCM, в то время как в новом ffmpeg aac считается форматом FLTP и выдает 32-битный вывод с плавающей запятой IEEE.

Таким образом, один и тот же код будет давать совершенно разные результаты с разными версиями ffmpeg. Затем, я хотел бы спросить, как правильно преобразовать аудио AAC в 16-битный PCM в новой версии?

Заранее большое спасибо!


person kaienfr    schedule 02.04.2014    source источник
comment
Почему бы не позволить FFmpeg сделать за вас работу и выводить 16-битный PCM?   -  person Brad    schedule 03.04.2014
comment
Подскажите пожалуйста как? Это должен быть аудиопоток. Здесь я провел тест из файла aac, чтобы легко проанализировать проблему, но результат тот же. Пожалуйста, объясните мне, как декодировать AAC и напрямую выводить 16-битный PCM? (в старом ffmpeg, по умолчанию это именно так, я это очень ценю) Большое спасибо!   -  person kaienfr    schedule 03.04.2014
comment
Это в коде, я не могу использовать внешний exe-файл. Подскажите, пожалуйста, как это закодировать с помощью ffmpeg. Спасибо!   -  person kaienfr    schedule 03.04.2014
comment
Есть много способов взаимодействия с FFmpeg. Я не могу вам помочь, но принцип тот же. Установите формат s16le и выходной аудиокодек pcm_s16le.   -  person Brad    schedule 03.04.2014
comment
Что ж, новый FFmpeg принудительно использует aac как формат FLTP, см., Например, здесь: github.com/libav/libav/blob/master/libavcodec/aacdec.c#L993, а старый нет, см. пример ffmpeg.org/doxygen/0.11/libavcodec_2aacdec_8c-source.html строка: 00878 и нет функции для изменения этого формата!   -  person kaienfr    schedule 03.04.2014
comment
Не могли бы вы помочь мне с примером, как конвертировать AAC в PCM? мы не предпочитаем ни dll, ни exe, допустим только исходный код или статическая ссылка на библиотеку. Большое спасибо.   -  person kaienfr    schedule 03.04.2014


Ответы (3)


Вы должны помнить, что AV_SAMPLE_FMT_FLTP - это планарный режим. Если ваш код ожидает вывода AV_SAMPLE_FMT_S16 (чередующийся режим), вам необходимо изменить порядок выборок после преобразования. Учитывая 2 аудиоканала и используя чередующийся режим, отсчеты упорядочиваются как «c0, c1, c0, c1, c0, c1, ...». Планарный режим: «c0, c0, c0, ..., c1, c1, c1, ...».

Аналогичный вопрос: Что чем разница между AV_SAMPLE_FMT_S16P и AV_SAMPLE_FMT_S16?

Подробности здесь: http://www.ffmpeg.org/doxygen/2.0/samplefmt_8h.html

person Rafael Cardoso    schedule 29.04.2014

Мне повезло, что я сделал нечто подобное. В вашем блоке кода

int nb_samples = frame_->nb_samples;
int channels = frame_->channels;
int outputBufferLen = nb_samples & channels * 2;
auto outputBuffer = (int16_t*)outbuf;

for (int i = 0; i < nb_samples; i++) {
   for (int c = 0; c < channels; c++) {
      float* extended_data = (float*)frame_->extended_data[c];
      float sample = extended_data[i];
      if (sample < -1.0f) sample = -1.0f;
      else if (sample > 1.0f) sample = 1.0f;
      outputBuffer[i * channels + c] = (int16_t)round(sample * 32767.0f);
   }

}

Попробуйте заменить на следующее:

int nb_samples = frame_->nb_samples;
int channels = frame_->channels;
int outputBufferLen = nb_samples & channels * 2;
auto outputBuffer = (int16_t*)outbuf;

for(int i=0; i < nb_samples; i++) {
   for(int c=0; c < channels; c++) {
      outputBuffer[i*channels+c] = (int16_t)(((float *)frame_->extended_data[c]) * 32767.0f);
   }
}
person VMcPherron    schedule 18.11.2014

Повторная выборка необходима только при преобразовании в другую частоту дискретизации. Если частота дискретизации такая же, вам нужно только преобразовать планарный формат с плавающей запятой в фиксированный 16-чередующийся формат.

person Robert Wallner    schedule 13.01.2015