Воспроизведение звука из видео с использованием FFmpeg и SDL_QueueAudio приводит к высокому звуку

#ffmpeg #sdl #sdl-2

#ffmpeg #sdl #sdl-2

Вопрос:

Я пытаюсь воспроизвести аудио из файла mp4 с использованием SDL2 и FFmpeg, и использование SDL_QueueAudio кажется намного проще, чем настройка обратного вызова.

Все решения, которые я нашел, будь то здесь или в руководствах dranger, устарели или используют обратные вызовы. Я пытался просмотреть все вопросы как с тегами ffmpeg, так и sdl (их не так много), но безрезультатно. Я попытался преобразовать руководство dranger для использования не устаревших вызовов, но столкнулся с той же проблемой. Я использую C, FFmpeg 4.1 и SDL 2.0.9.

Это настройка для avcodecontext и AVCodec:

     int audioStream = -1;
    for (i = 0; i < formatContext->nb_streams; i  ) {
        if (audioStream < 0 amp;amp; formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
        }
    }

    AVCodecParameters *audioParams = formatContext->streams[audioStream]->codecpar;

    AVCodec *audioCodec = avcodec_find_decoder(audioParams->codec_id);

    AVCodecContext *audioCodecCtx = avcodec_alloc_context3(NULL);
    avcodec_open2(audioCodecCtx, audioCodec, NULL);

    SDL_Init(SDL_INIT_AUDIO)

    SDL_AudioSpec desired, obtained;
    SDL_zero(desired);
    SDL_zero(obtained);
    desired.freq = audioCodecCtx->sample_rate;
    desired.format = AUDIO_F32SYS;
    desired.channels = audioCodecCtx->channels;
    desired.silence = 0;
    desired.samples = AUDIO_BUFFER_SIZE;

    SDL_AudioDeviceID audioDevice = SDL_OpenAudioDevice(NULL, 0, amp;desired, amp;obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
  

Это основной цикл декодирования пакетов:

     while (av_read_frame(formatContext, amp;packet) >= 0) {
        if (packet.stream_index == audioStream) {
            if (!avcodec_send_packet(audioCodecCtx, amp;packet)) {
                avcodec_receive_frame(audioCodecCtx, audioFrame);
                SDL_QueueAudio(audioDevice, audioFrame->data[0], audioFrame->linesize[0]);
            }
        }
    }
  

Звук воспроизводится с правильной скоростью, но на гораздо более высоком уровне, чем он есть на самом деле. Я бы хотел, чтобы он звучал так же, как в любом медиаплеере.
Редактировать: Я только что понял, что в тестовом видео есть стереозвук, но я стою только в очереди audioFrame.data[0] , что, как я полагаю, означает, что я играю только на одном канале. Я попробовал постановку в очередь, audioFrame.data[1] в которой также есть данные, но это не решило проблему. Прав ли я, и если да, то как мне воспроизвести оба канала?

Комментарии:

1. Вы проверили значения в obtained структуре (в основном частоту)?

2. Для моего тестового видео obtained.freq равно 44100, что совпадает с тем, что сообщает VLC.

Ответ №1:

Возможно, уже слишком поздно отвечать на этот вопрос, но я столкнулся с той же проблемой, и теперь я нашел решение, которое работает для меня, поэтому я публикую это.
Проблема здесь, вероятно, в том, что формат аудио, декодируемый FFmpeg, является форматом AV_SAMPLE_FMT_FLTP (float planer), где каналы хранятся отдельно, как frame->data[0] и frame->data[1] .
Нам нужно преобразовать его в формат, который объединяет эти каналы в один массив, используя swr_convert()
И вот мое решение.

  1. Настройка SwrContext
 SwrContext *resampler = swr_alloc_set_opts(NULL, 
                                           audioCodecCtx->channel_layout,
                                           AV_SAMPLE_FMT_S16,
                                           44100,
                                           audioCodecCtx->channel_layout,
                                           audioCodecCtx->sample_fmt,
                                           audioCodecCtx->sample_rate,
                                           0, 
                                           NULL);
swr_init(resampler);
  
  1. Настройка звука SDL
 SDL_AudioDeviceID dev;
SDL_AudioSpec want, have;
SDL_zero(want);
SDL_zero(have);
want.freq = 44100;
want.channels = audioCodecCtx->channels;
want.format = AUDIO_S16SYS;
dev = SDL_OpenAudioDevice(NULL, 0, amp;want, amp;have, 0);
SDL_PauseAudioDevice(dev, 0);
  

наконец, цикл декодирования

 int ret = 0;
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
AVFrame *audioframe = av_frame_alloc();
while (true){
    ret = av_read_frame(formatContext, packet);
    if (ret < 0) break;
    AVStream *stream = formatContext->streams[packet->stream_index];
    if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
        ret = avcodec_send_packet(audioCodecCtx, packet);
        while (ret >= 0){
            ret = avcodec_receive_frame(audioCodecCtx, frame);
            if (ret >= 0){
                int dst_samples = frame->channels * av_rescale_rnd(
                                   swr_get_delay(resampler, frame->sample_rate)
                                     frame->nb_samples,
                                   44100,
                                   frame->sample_rate,               
                                   AV_ROUND_UP);
                uint8_t *audiobuf = NULL;
                ret = av_samples_alloc(amp;audiobuf, 
                                       NULL, 
                                       1, 
                                       dst_samples,
                                       AV_SAMPLE_FMT_S16, 
                                       1);
                dst_samples = frame->channels * swr_convert(
                                                 resampler, 
                                                 amp;audiobuf, 
                                                 dst_samples,
                                                 (const uint8_t**) frame->data, 
                                                 frame->nb_samples);
                ret = av_samples_fill_arrays(audioframe->data, 
                                             audioframe->linesize, 
                                             audiobuf,
                                             1, 
                                             dst_samples, 
                                             AV_SAMPLE_FMT_S16, 
                                             1);
                SDL_QueueAudio(dev, 
                               audioframe->data[0], 
                               audioframe->linesize[0]); 
            }
        }
    }
}
  

Ответ №2:

Что сработало для меня, так это настройка частоты. Попробуйте изменить частоту на что-то вроде audioCodecCtx->sample_rate * 0.5 .