Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.89/18: Рейтинг темы: голосов - 18, средняя оценка - 4.89
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140

Конвертация аудио сэмплов wasapi в AVFrame FFmpeg и C++

04.02.2021, 04:10. Показов 4008. Ответов 17

Студворк — интернет-сервис помощи студентам
Задача заключается в следующем, необходимо аудио сэмплы, которые захватываются с помощью WASAPI записать в аудио файл ac3 с помощью FFmpeg. Сэмплы захватываются в виде BYTE *, их нужно сначала конвертировать в AVFrame и в дальнейшем с помощью функции av_interleaved_write_frame записать в файл.

WASAPI пишет семплы в формате, значения структуры WAVEFORMATEXTENSIBLE:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
       wfex    @0x207c182fe30  WAVEFORMATEXTENSIBLE
            Format  @0x207c182fe30  tWAVEFORMATEX
                cbSize  22  unsigned short
                nAvgBytesPerSec 384000  unsigned long
                nBlockAlign 8   unsigned short
                nChannels   2   unsigned short
                nSamplesPerSec  48000   unsigned long
                wBitsPerSample  32  unsigned short
                wFormatTag  65534   unsigned short
            Samples @0x207c182fe42  WAVEFORMATEXTENSIBLE::<unnamed-type-Samples>
                wReserved   32  unsigned short
                wSamplesPerBlock    32  unsigned short
                wValidBitsPerSample 32  unsigned short
            SubFormat   @0x207c182fe48  _GUID
                Data1   3   unsigned long
                Data2   0   unsigned short
                Data3   16  unsigned short
                Data4   "�\000\000\0008�q" unsigned char[8]
                                128 0x80    unsigned char
                        '\0'    0   0x00    unsigned char
                        '\0'    0   0x00    unsigned char
                        'ª'     170 0xaa    unsigned char
                        '\0'    0   0x00    unsigned char
                        '8'     56  0x38    unsigned char
                                155 0x9b    unsigned char
                        'q'     113 0x71    unsigned char
            dwChannelMask   3   unsigned long
А кодек AC3 у FFmpeg кодируется в формате AV_SAMPLE_FMT_FLTP

Вот код WASAPI по захвату сэмплов (сэмплы пишутся в вектор m_curAudioData):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
DWORD AudioCapture::recordAudio()
{
    HRESULT hr;
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
 
    hr = pCaptureClient->GetNextPacketSize(&packetLength);
    if (FAILED(hr))
    {
        printf("Faild to get next data packet size\n");
        return hr;
    }
    while (packetLength != 0)
    {
        // Get the available data in the shared buffer.
        hr = pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &pos, &ts);
        if (FAILED(hr))
        {
            printf("Faild to get buffer\n");
            return hr;
        }
        if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
        {
             pData = NULL;  // Tell CopyData to write silence.
             BYTE * buffer = new BYTE[(numFramesAvailable * pwfx->nBlockAlign)];
             memcpy(buffer, 0, numFramesAvailable * pwfx->nBlockAlign);
             m_curAudioData.push_back({buffer, numFramesAvailable, ts});
        }
        else
        {
            BYTE * buffer = new BYTE[(numFramesAvailable * pwfx->nBlockAlign)];
            memcpy(buffer, pData, numFramesAvailable * pwfx->nBlockAlign);
            m_curAudioData.push_back({buffer, numFramesAvailable, ts});
        }
 
        hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
        if (FAILED(hr)) {
            printf("Faild to release buffer\n");
            return hr;
        }
 
        hr = pCaptureClient->GetNextPacketSize(&packetLength);
        if (FAILED(hr)) {
            printf("Faild to get next data packet size\n");
            return hr;
        }
    }
    return 0;
}
А вот часть кода FFmpeg по настройке выходного контекста и выходного стрима аудио.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
AVStream * VideoEncoder::AddAudioStream(AVFormatContext *pContext, AVCodecID codec_id)
{
    AVCodecContext *pCodecCxt = NULL;
    AVStream *pStream = NULL;
 
    // Try create stream.
    pStream = avformat_new_stream(pContext, NULL);
    if (!pStream)
    {
        printf("Cannot add new audio stream\n");
        return NULL;
    }
 
    // Codec.
    pCodecCxt = pStream->codec;
    pCodecCxt->codec_id = codec_id;
    pCodecCxt->codec_type = AVMEDIA_TYPE_AUDIO;
    // Set format
    pCodecCxt->bit_rate    = mWFX->wBitsPerSample;
    pCodecCxt->sample_rate = mWFX->nSamplesPerSec;
    pCodecCxt->channels    = mWFX->nChannels; // mono
    pCodecCxt->sample_fmt  = AV_SAMPLE_FMT_FLTP;
 
    // Some formats want stream headers to be separate.
    if(pContext->oformat->flags & AVFMT_GLOBALHEADER)
    {
        pCodecCxt->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
 
    /*nSizeAudioEncodeBuffer = 4 * MAX_AUDIO_PACKET_SIZE;
    if (pAudioEncodeBuffer == NULL)
    {
        pAudioEncodeBuffer = (uint8_t * )av_malloc(nSizeAudioEncodeBuffer);
    }*/
 
    mSamples = (uint8_t*)av_malloc(2 * mBufferSize);
    mSamplesEnd = mSamples + mBufferSize;
    mCurrentFrame = mSamples;
 
    return pStream;
}
 
bool VideoEncoder::OpenAudio(AVFormatContext *pContext, AVStream *pStream)
{
    AVCodecContext *pCodecCxt = NULL;
    AVCodec *pCodec = NULL;
    pCodecCxt = pStream->codec;
 
    // Find the audio encoder.
    pCodec = avcodec_find_encoder(pCodecCxt->codec_id);
    if (!pCodec)
    {
        printf("Cannot open audio codec\n");
        return false;
    }
 
    // Open it.
    if (avcodec_open2(pCodecCxt, pCodec, NULL) < 0)
    {
        printf("Cannot open audio codec\n");
        return false;
    }
 
    return true;
}
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
04.02.2021, 04:10
Ответы с готовыми решениями:

Ffmpeg сохранить AVFrame в .bmp или .jpg
Контекст конвертирования создаю так: imgconvertCtx = sws_getCachedContext( imgconvertCtx, codecCtx -&gt; width, codecCtx -&gt; height, ...

ffmpeg конвертация файла
Всем привет! Делаю первые шаги в ffmpeg'e. Для начала просто решил без конвертации раскодировать один файл и сделать другой. Вот код ...

Перекодирование аудио ffmpeg
Здравствуйте! Подскажите пожалуйста кто-нибудь, как можно перекодировать аудио из 711 кодека (файл .PCMA) в 722 кодек (файл .G722) с...

17
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 11:35
Серега_123, так вопрос то ваш в чём заключается? Очень уж размытая формулировка.
Вы не смогли разобрать исходный формат указанный в WAVEFORMATEXTENSIBLE?
Или не знаете функции конвертирования звука между разными некомпрессированными форматами (swr_convert)?
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
04.02.2021, 11:50  [ТС]
Ygg, прошу прощения, что из поста непонятно что я хотел. Я буду очень признателен за минимально работающий пример метода, который бы принимал на вход данные этого формата в виде байтов, а отдавал AVFrame готовый к записи в файл ac3 с помощью av_interleaved_write_frame.

Что то типа такого: PCM2AC3(BYTE * data, AVFrame * frame);
Как то так
0
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 12:16
Серега_123, если вы смогли интерпретировать исходный формат, то не могли бы вы его озвучить? Ваша интерпретации дампа памяти структуры отображает GUID не в лучшем виде и её сложно анализировать. Полагаю, что GUID соответствует KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ("00000003-0000-0010-8000-00aa00389b71").
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
04.02.2021, 13:32  [ТС]
Ygg, Так, ну я распечатал формат таким образом:
C++
1
2
3
4
5
6
7
auto wfex = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(pwfx);
const auto format = wfex->SubFormat;
 
printf("Guid = {%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}",
    format.Data1, format.Data2, format.Data3,
    format.Data4[0], format.Data4[1], format.Data4[2], format.Data4[3],
    format.Data4[4], format.Data4[5], format.Data4[6], format.Data4[7]);
значение получилось: Guid = {00000003-0000-0010-8000-00AA00389B71}


что то в документации я его найти не могу

Возможно неверно распечатал.

нагуглил:

IEEE floating-point audio {00000003-0000-0010-8000-00AA00389B71} (MEDIASUBTYPE_IEEE_FLOAT) 0x0003 (WAVE_FORMAT_IEEE_FLOAT)
1
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 13:50
Значит WASAPI отдаёт вам стерео (nChannels=2) в формате float. В терминологии ffmpeg данный формат называется AV_SAMPLE_FMT_FLT. Если кодек ac3 требует на вход данные исключительно в формате AV_SAMPLE_FMT_FLTP, то исходные данные нужно преобразовать.

Названия форматов отличаются одной буквой 'P' в конце. Наличие этой буквы означает планарный формат. У вас стерео, т.е. два канала звука. При непланарном представлении семплы каналов перемежаются и лежат в одном буфере. Первым идёт семпл левого канала, дальше первый семпл правого канала, затем второй семпл левого канала и так далее. При планарном представлении внутри AVFrame создаются два буфера и каждый из них содержит семплы только одного канала.
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
04.02.2021, 14:00  [ТС]
Ygg, Т.е. мне один буфер надо разделить на два? нечетные в один буфер, четные в другой. На выходе получится два буфера? Это через цикл я так понимаю.
0
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 14:14
Цитата Сообщение от Серега_123 Посмотреть сообщение
Это через цикл я так понимаю.
Если вы хотите сделать это самостоятельно, то через цикл. В ffmpeg есть специальный функционал для преобразования разных несжатых форматов звука (AV_SAMPLE_FMT_???) между собой. Можно воспользоваться этим функционалом.

Преобразование средствами ffmpeg выполняется в два этапа. Сперва нужно подготовить контекст, указать какой формат в какой вы хотите преобразовывать. Затем уже для преобразования используется этот контекст и буфера с данными.

Подготовку контекста можно найти в исходниках различных примеров, например тут:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
         /* create resampler context */
         ost->swr_ctx = swr_alloc();
         if (!ost->swr_ctx) {
             fprintf(stderr, "Could not allocate resampler context\n");
             exit(1);
         }
 
         /* set options */
         av_opt_set_int       (ost->swr_ctx, "in_channel_count",   c->channels,       0);
         av_opt_set_int       (ost->swr_ctx, "in_sample_rate",     c->sample_rate,    0);
         av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt",      AV_SAMPLE_FMT_S16, 0);
         av_opt_set_int       (ost->swr_ctx, "out_channel_count",  c->channels,       0);
         av_opt_set_int       (ost->swr_ctx, "out_sample_rate",    c->sample_rate,    0);
         av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt",     c->sample_fmt,     0);
 
         /* initialize the resampling context */
         if ((ret = swr_init(ost->swr_ctx)) < 0) {
             fprintf(stderr, "Failed to initialize the resampling context\n");
             exit(1);
         }
Процесс преобразования с использованием контекста можно подсмотреть там же.
C++
1
2
3
4
5
6
7
8
9
10
static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
{//...
             /* convert to destination format */
             ret = swr_convert(ost->swr_ctx,
                               ost->frame->data, dst_nb_samples,
                               (const uint8_t **)frame->data, frame->nb_samples);
             if (ret < 0) {
                 fprintf(stderr, "Error while converting\n");
                 exit(1);
             }
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
04.02.2021, 14:19  [ТС]
Ygg, Это ужасные примеры. Из них вообще ничего непонятно.... имена переменных просто одна буква . Т.е. контекста должно быть два, один формата на входе, второй формата на выходе?
0
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 14:50
Цитата Сообщение от Серега_123 Посмотреть сообщение
Это ужасные примеры. Из них вообще ничего непонятно.... имена переменных просто одна буква
Привыкайте, обычный код для ffmpeg.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
         /* create resampler context */
         ost->swr_ctx = swr_alloc(); // создаём контекст, но он пока ещё пустой, его нужно будет наполнить
         if (!ost->swr_ctx) {
             fprintf(stderr, "Could not allocate resampler context\n");
             exit(1);
         }
 
         /* set options */
         // опции - это такая текстовая мэпа внутри контекста, содержит ключи и значения для клучей
         // первый аргумент - контекст
         // второй аргумент - ключ, если начинается с in, то относится к входному формату, out - к выходному
         // третий аргумент - значение для ключа
         av_opt_set_int       (ost->swr_ctx, "in_channel_count",   c->channels,       0); // количество каналов исходного формата
         av_opt_set_int       (ost->swr_ctx, "in_sample_rate",     c->sample_rate,    0); // частота дискретизации исходного
         av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt",      AV_SAMPLE_FMT_S16, 0); // формат семпла исходного
         av_opt_set_int       (ost->swr_ctx, "out_channel_count",  c->channels,       0); // аналогично, только для выхода
         av_opt_set_int       (ost->swr_ctx, "out_sample_rate",    c->sample_rate,    0);
         av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt",     c->sample_fmt,     0);
 
         /* initialize the resampling context */
         // теперь применяем текстовую мэпу, реально считываем настройки форматов и готовим контекст
         if ((ret = swr_init(ost->swr_ctx)) < 0) {
             fprintf(stderr, "Failed to initialize the resampling context\n");
             exit(1);
         }
C++
1
2
3
4
5
6
7
8
9
10
static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
{//...
             /* convert to destination format */
             ret = swr_convert(ost->swr_ctx, // контекст преобразования, который мы настраивали в начале
                               ost->frame->data, dst_nb_samples, // выходные данные, буфера и количество семплов
                               (const uint8_t **)frame->data, frame->nb_samples); // входные данные, буфера и количество семплов
             if (ret < 0) {
                 fprintf(stderr, "Error while converting\n");
                 exit(1);
             }
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
04.02.2021, 16:54  [ТС]
Ygg, У меня еще такой вопрос: я правильно понял, что контекст один? Т.е. мы не настраиваем два контекста входящий и исходящий, а по сути настраиваем один исходящий контекст? Или это контекст стрима?

UPD: кажется я понял, это контекст преобразования. Т.е. это не исходящий контекст AVCodecContext, а SwrContext
1
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
04.02.2021, 17:08
Серега_123, контекст для преобразования звука из одного формата в другой формат один SwrContext.

В ffmpeg всё в основном делается через контексты. Одни контексты для кодирования, другие для чтения файлов, третьи для преобразований форматов. Контексты как хендлы в windows. Есть хендл окна и есть функции для работы с окном, в которые передаётся этот хендл. Есть хендл файла, а есть функции для работы с файлами, в которые передаётся этот хендл. Для C++ привычнее было бы объединить данные с функциями в класс, тогда можно было бы вызывать сразу функции контекста. Но вот почему-то разработчиками был избран иной путь, который ближе к языку C.
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
05.02.2021, 08:24  [ТС]
Ygg, я вчера по примерам накидал метод. Не могли бы вы код ревью его провести, на ошибки указать, советы дать. Это прототип, который не работает:
Вот сам метод:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
bool VideoEncoder::AddAudioSample(AVFormatContext * pFormatContext, AVStream * pStream, BYTE * audiodata, uint32_t sampleCount, uint64_t devicets)
{
    bool res = true;
 
    AVCodecContext * pCodecCxt = NULL;
    pCodecCxt = pStream->codec;
 
    //Создаю фрейм для входного формата
    AVFrame *  pFLTAudioFrame = NULL;
    pFLTAudioFrame = av_frame_alloc();
    pFLTAudioFrame ->nb_samples = sample_count;
    pFLTAudioFrame ->format = AV_SAMPLE_FMT_FLT;
    pFLTAudioFrame ->sample_rate = mWFX->nSamplesPerSec;
    pFLTAudioFrame ->channels = mWFX->nChannels;
    pFLTAudioFrame ->pkt_size =  pFLTAudioFrame->nb_samples*mWFX->nBlockAlign;
 
    av_samples_fill_arrays(pFLTAudioFrame ->data, pFLTAudioFrame ->linesize, (const uint8_t  *)data, pFLTAudioFrame->channels, pFLTAudioFrame ->nb_samples, AV_SAMPLE_FMT_FLT, 1);
 
    int dst_nb_samples = 0;
 
    dst_nb_samples = av_rescale_rnd(swr_get_delay(pSmplConvertCtx, pStream->codec->sample_rate) + pFLTAudioFrame->nb_samples, pStream->codec->sample_rate,  pStream->codec->sample_rate, AV_ROUND_UP);
    
    //Создаю исходный кадр
    AVFrame*  pFLTPAudioFrame = NULL;
    pFLTPAudioFrame = av_frame_alloc();
    pFLTPAudioFrame->nb_samples = pFLTAudioFrame->nb_samples;
    pFLTPAudioFrame->format = AV_SAMPLE_FMT_FLTP;
    pFLTPAudioFrame->sample_rate = pFLTAudioFrame->sample_rate;
    pFLTPAudioFrame->channels = pFLTAudioFrame->channels;
    pFLTPAudioFrame->pkt_size = pFLTAudioFrame->nb_samples*mWFX->nBlockAlign;
 
    //Конвертирую форматы
    swr_convert(pSmplConvertCtx, pFLTPAudioFrame->data, dst_nb_samples, (const uint8_t **)pFLTAudioFrame->data, pFLTAudioFrame->nb_samples);
 
 
    //Создаю пакет и дальше его записываю
    AVPacket pkt;
    av_init_packet(&pkt);
 
    pkt.data = pAudioEncodeBuffer;
    pkt.size = nSizeAudioEncodeBuffer;
    pkt.pts = av_rescale_q(pCodecCxt->coded_frame->pts, pCodecCxt->time_base, pStream->time_base);
 
    int gotOutput = 0;
    int ret = avcodec_encode_audio2(pCodecCxt, &pkt, pFLTPAudioFrame, &gotOutput);
    if (ret < 0)
    {
      std::cout << "Error encoding frame in line " << __LINE__ << std::endl;
      return false;
    }
    if (gotOutput)
    {
      if (av_interleaved_write_frame(pFormatContext, &pkt) != 0)
      {
          return false;
      }
      av_free_packet(&pkt);
    }
 
    do
    {
      ret = avcodec_encode_audio2(pCodecCxt, &pkt, NULL, &gotOutput);
      if (ret < 0)
      {
        std::cout << "Error encoding frame in line " << __LINE__ << std::endl;
        return false;
      }
      if (gotOutput)
      {
          if (av_interleaved_write_frame(pFormatContext, &pkt) != 0)
          {
              break;
          }
        av_free_packet(&pkt);
      }
 
    } while (gotOutput);
 
    return res;
}
Вот так я настраиваю контекст и вычисляю размер буфера кадра:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    pSmplConvertCtx = swr_alloc();
    if (!pSmplConvertCtx)
    {
         fprintf(stderr, "Could not allocate resampler context\n");
         exit(1);
    }
 
    /* set options */
    av_opt_set_int       (pSmplConvertCtx, "in_channel_count",   pCodecCxt->channels,       0);
    av_opt_set_int       (pSmplConvertCtx, "in_sample_rate",     pCodecCxt->sample_rate,    0);
    av_opt_set_sample_fmt(pSmplConvertCtx, "in_sample_fmt",      AV_SAMPLE_FMT_FLT,         0);
    av_opt_set_int       (pSmplConvertCtx, "out_channel_count",  pCodecCxt->channels,       0);
    av_opt_set_int       (pSmplConvertCtx, "out_sample_rate",    pCodecCxt->sample_rate,    0);
    av_opt_set_sample_fmt(pSmplConvertCtx, "out_sample_fmt",     pCodecCxt->sample_fmt,     0);
 
    /* initialize the resampling context */
    if ((swr_init(pSmplConvertCtx)) < 0)
    {
        fprintf(stderr, "Failed to initialize the resampling context\n");
        exit(1);
    }
 
    nSizeAudioEncodeBuffer = av_samples_get_buffer_size(NULL, pCodecCxt->channels, pCodecCxt->sample_rate, pCodecCxt->sample_fmt, 0);
    if (pAudioEncodeBuffer == NULL)
    {
        pAudioEncodeBuffer = (uint8_t * )av_malloc(nSizeAudioEncodeBuffer);
    }
Выкидывает исключение на моменте конвертации: swr_convert

Вроде же логика верная. В чем ошибка?

Что то тут наверное надо делать с разделением буфера?
0
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
05.02.2021, 10:07
Серега_123, я не настолько профи, что бы выявить все ошибки по фрагменту кода. По хорошему, нужно иметь возможность всё это запускать и отлаживать. Могу указать на основные неточности, которые сходу бросаются в глаза.

Основная ошибка, судя по вашему описанию и коду, в отсутствии результирующих буферов. При вызове swr_convert указатели буферов pFLTPAudioFrame->data[0] и pFLTPAudioFrame->data[1] должны быть не нулевыми.

AVFrame - это структура для хранения видео кадра или набора звуковых семплов. Структура содержит большое количество описательных полей, но сама не является буфером для данных. Память под буфер выделяется динамически и указатели хранятся в массиве data. Массив используется для хранения нескольких буферов при планарных форматах. data[0] - буфер первого плана, data[1] - буфер второго плана и так далее.

Так как код ориентирован на C, то структура не имеет конструктора и деструктора. Вместо конструктора выступает функция av_frame_alloc. Эта функция выделяет память под структуру и инициализирует поля структуры значениями по умолчанию. Создать буфера на этом этапе нельзя, так как размеры буферов и их количество определяются форматом и прочими параметрами, которые пока не заданы. Для кадра исходного формата вы вызываете функцию av_samples_fill_arrays, полагаю она и производит инициализацию буферов и копирование в них данных. Для кадра выходного формата ничего по созданию буферов не вызывается.

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

Функция av_rescale_rnd вам скорее всего не нужна. Она используется при изменении частоты дискретизации. При этом получается, что исходное количество семплов преобразуется в другое количество семплов. Если же вы сохраняете частоту дискретизации, то количество семплов до и после преобразования у вас будет одинаковым.

Цикл do с 60 строки по 77 сделан не к месту. Это так называемый цикл выталкивания, который должен запускаться один раз после того как все входные данные будут прочитаны и отправлены в энкодер. Кодеки могут задерживать данные в себе. К примеру, вы помещаете в кодек первый свой фрагмент данных, кодек говорит ok, но никаких данных не отдаёт (gotOutput=0). Вы помещаете в кодек второй фрагмет данных, кодек говорит ok и отдаёт вам данные соответствующие первому фрагменту. Возникает такой временной сдвиг между входом и выходом. В конце, когда больше уже нет новых фрагментов для передачи их кодеку, кодек содержит в себе ещё некоторое количество задержанных данных. Эти данные нужно из него вытолкнуть. Для этого вызывают функцию кодирования avcodec_encode_audio2, но вместо исходного фрагмента данных передаёт в неё нулевой указатель. Выталкивание повторяют до тех пор, пока функция кодирования возвращает данные.
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
05.02.2021, 15:09  [ТС]
Ygg, Мне конечно неловко просить Вас. Я сегодня снова весь день просидел с задачей и не смог добиться результата. Зашел в тупик. Если у вас есть возможность помочь мне с доработкой моего прототипа, я был бы вам очень благодарен.
0
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
06.02.2021, 08:36  [ТС]
Ygg, Вот прототип. Я прошу у вас помощи в его доработке, т.е. как я сам зашел в тупик
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
bool AddAudioSample(AVFormatContext * pFormatContext, AVStream * pStream, BYTE * audiodata, uint32_t sampleCount, uint64_t devicets)
{
    AVCodecContext * pCodecCxt = NULL;
    bool res = true;
 
    pCodecCxt = pStream->codec;
 
    AVFrame*  pFLTAudioFrame = NULL;
    pFLTAudioFrame = av_frame_alloc();
 
    AVFrame*  pFLTPAudioFrame = NULL;
    pFLTPAudioFrame = av_frame_alloc();
 
    ProcessData(pFLTAudioFrame, pFLTPAudioFrame, (uint8_t *)audiodata, sampleCount, devicets);
 
    swr_convert(pSmplConvertCtx, pFLTPAudioFrame->data, pFLTPAudioFrame->nb_samples, (const uint8_t **)pFLTAudioFrame->data, pFLTAudioFrame->nb_samples);
 
    AVPacket pkt;
    av_init_packet(&pkt);
 
    pkt.flags |= AV_PKT_FLAG_KEY;
    pkt.stream_index = pStream->index;
    pkt.data = pAudioEncodeBuffer;
    pkt.size = pFLTPAudioFrame->pkt_size;
 
    int gotOutput = 0;
    auto ret = avcodec_encode_audio2(pCodecCxt, &pkt, pFLTPAudioFrame, &gotOutput);
    if (ret < 0)
    {
        exit(1);
    }
    if (gotOutput)
    {
        pkt.pts = av_rescale_q(pCodecCxt->coded_frame->pts, pCodecCxt->time_base, pStream->time_base);
        ret = av_interleaved_write_frame(pFormatContext, &pkt);
        if (ret < 0)
        {
            exit(1);
        }
    }
 
    return res;
}
 
void ProcessData(AVFrame *inputframe, AVFrame *outputFrame, uint8_t* data, uint32_t sample_count, uint64_t device_ts)
{
    inputframe->nb_samples = sample_count;
    inputframe->format = AV_SAMPLE_FMT_FLT;
    inputframe->sample_rate = mWFX->nSamplesPerSec;
    inputframe->channels = mWFX->nChannels;
    inputframe->pkt_size = sample_count*mWFX->nBlockAlign;
 
    av_samples_fill_arrays(inputframe->data, inputframe->linesize, data, mWFX->nChannels, sample_count, AV_SAMPLE_FMT_FLT, 1);
 
    outputFrame->nb_samples = inputframe->nb_samples;
    outputFrame->format = AV_SAMPLE_FMT_FLTP;
    outputFrame->sample_rate = inputframe->sample_rate;
    outputFrame->channels = inputframe->channels;
    outputFrame->pkt_size = sample_count*mWFX->nBlockAlign;
 
    av_samples_fill_arrays(outputFrame->data, outputFrame->linesize, pAudioEncodeBuffer, mWFX->nChannels, sample_count, AV_SAMPLE_FMT_FLTP, 1);
}
Добавлено через 2 часа 52 минуты
Ygg, Если поможете написать, о цене можем договориться.
0
2735 / 890 / 331
Регистрация: 10.02.2018
Сообщений: 2,109
06.02.2021, 09:23
Вот пример нужного вам преобразования, кодирования и записи в файл.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
//#pragma warning(disable : 4996)
//#pragma warning(disable : 4703)
 
#define _USE_MATH_DEFINES
#include "math.h"
 
//--------------------------------------------------------------------------
extern "C"
{
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
}
 
//--------------------------------------------------------------------------
const int channels = 2;
const int sample_rate = 48000;
const int sample_cnt = 1000;
 
//--------------------------------------------------------------------------
struct GlobalContext
{
    AVFrame* frame;
    AVFormatContext* format;
    AVCodecContext* codec;
    SwrContext* swr;
    long long pos;
};
 
//--------------------------------------------------------------------------
// заполнить буфер <buffer> звуковыми семплами в формате FLT
// <samples> - количество семплов помещаемых в буфер
//--------------------------------------------------------------------------
void GetSourceData(void* buffer, int samples)
{
    static int pos[4] = { 0, 0, 0, 0 };
    static int channels_freq[4] = { 500, 1000, 2000, 3000 };
    static double channels_ampl[4] = { 0.2, 0.5, 0.3, 0.8 };
 
    float* flt = (float*)buffer;
 
    for (int i = 0; i < samples; i++)
    {
        for (int j = 0; j < channels; j++)
        {
            *flt++ = float(channels_ampl[j] * sin(pos[j]++ * M_PI * channels_freq[j] / sample_rate));
        }
    }
}
 
//--------------------------------------------------------------------------
// инициализация энкодера
//--------------------------------------------------------------------------
void InitEncoder(GlobalContext& ctx)
{
    AVCodec* codec = avcodec_find_encoder_by_name("ac3");
    if (!codec)
    {
        printf("avcodec_find_encoder_by_name(AC3) - failed\n");
        exit(1);
    }
 
    ctx.codec = avcodec_alloc_context3(codec);
    if (!ctx.codec)
    {
        printf("avcodec_alloc_context3() - failed\n");
        exit(1);
    }
 
    ctx.codec->channels = channels;
    ctx.codec->channel_layout = av_get_default_channel_layout(channels);
    ctx.codec->sample_rate = sample_rate;
    ctx.codec->sample_fmt = codec->sample_fmts[0];// AV_SAMPLE_FMT_FLTP;
 
    if (avcodec_open2(ctx.codec, codec, NULL) < 0)
    {
        printf("avcodec_open2() - failed\n");
        exit(1);
    }
}
 
//--------------------------------------------------------------------------
// инициализация кадра звука для отправки в энкодер
//--------------------------------------------------------------------------
void InitFrame(GlobalContext& ctx)
{
    ctx.frame = av_frame_alloc();
 
    ctx.frame->format = ctx.codec->sample_fmt;
    ctx.frame->sample_rate = ctx.codec->sample_rate;
    ctx.frame->channels = ctx.codec->channels;
    ctx.frame->channel_layout = ctx.codec->channel_layout;
    ctx.frame->nb_samples = ctx.codec->frame_size;
 
    if (av_samples_alloc(ctx.frame->data, ctx.frame->linesize, ctx.frame->channels, ctx.frame->nb_samples, ctx.codec->sample_fmt, 0) < 0)
    {
        printf("av_samples_alloc() - failed\n");
        exit(1);
    }
}
 
//--------------------------------------------------------------------------
// инициализация преобразователя форматов
//--------------------------------------------------------------------------
void InitResampler(GlobalContext& ctx)
{
    ctx.swr = swr_alloc();
    if (!ctx.swr)
    {
        printf("Could not allocate resampler context\n");
        exit(1);
    }
 
    av_opt_set_int(ctx.swr, "in_channel_count", channels, 0);
    av_opt_set_int(ctx.swr, "in_sample_rate", sample_rate, 0);
    av_opt_set_sample_fmt(ctx.swr, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
    av_opt_set_int(ctx.swr, "out_channel_count", channels, 0);
    av_opt_set_int(ctx.swr, "out_sample_rate", sample_rate, 0);
    av_opt_set_sample_fmt(ctx.swr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
 
    if (swr_init(ctx.swr) < 0)
    {
        printf("Failed to initialize the resampling context\n");
        exit(1);
    }
}
 
//--------------------------------------------------------------------------
// инициализация записи в файл
//--------------------------------------------------------------------------
void InitFile(GlobalContext& ctx, const char* fileName)
{
    avformat_alloc_output_context2(&ctx.format, NULL, NULL, fileName);
    if (!ctx.format)
    {
        printf("avformat_alloc_output_context2() - failed\n");
        exit(1);
    }
 
    AVStream* stream = avformat_new_stream(ctx.format, 0);
    if (!stream)
    {
        printf("avformat_new_stream() - failed\n");
        exit(1);
    }
 
    avcodec_parameters_from_context(stream->codecpar, ctx.codec);
 
    av_dump_format(ctx.format, 0, fileName, 1);
 
    if (avio_open(&ctx.format->pb, fileName, AVIO_FLAG_WRITE) < 0)
    {
        printf("avio_open() - failed\n");
        exit(1);
    }
 
    avformat_write_header(ctx.format, NULL);
}
 
//--------------------------------------------------------------------------
// процедура преобразования формата, кодирования и записи одного кадра
//--------------------------------------------------------------------------
void Resample(GlobalContext& ctx, const void* buffer = 0, int samples = 0)
{
    int cnt = 0;
 
    // обработать один новый кадр звука
    if (buffer && samples)
    {
        // помещаем данные в контекст преобразования, но ничего не забираем (буферизация)
        swr_convert(ctx.swr, 0, 0, (const uint8_t**)&buffer, samples);
 
        // забираем данные из контекства преобразования нужными кодеку порциями
        while ((cnt = swr_get_out_samples(ctx.swr, 0)) >= ctx.codec->frame_size)
        {
            // преобразование
            cnt = swr_convert(ctx.swr, ctx.frame->data, ctx.codec->frame_size, 0, 0);
            if (cnt <= 0)
            {
                printf("swr_convert() - failed");
                exit(1);
            }
 
            AVPacket pkt = {};
            av_init_packet(&pkt);
 
            // тайм стемп по номеру семпла
            ctx.frame->pts = ctx.pos;
            ctx.pos += ctx.frame->nb_samples;
 
            // кодирование
            int gotPacket = 0;
            if (avcodec_encode_audio2(ctx.codec, &pkt, ctx.frame, &gotPacket) < 0)
            {
                printf("avcodec_encode_audio2() - failed");
                exit(1);
            }
 
            if (gotPacket)
            {
                // запись
                if (av_write_frame(ctx.format, &pkt) < 0)
                {
                    printf("av_write_frame() - failed");
                    exit(1);
                }
            }
 
            av_free_packet(&pkt);
        }
    }
    // завершение обработки, выполнить выталкивание данных
    else
    {
        AVPacket pkt = {};
        av_init_packet(&pkt);
 
        int gotPacket = 0;
 
        do
        {
            // кодирование (выталкивание)
            if (avcodec_encode_audio2(ctx.codec, &pkt, NULL, &gotPacket) < 0)
            {
                printf("av_write_frame() - failed");
                exit(1);
            }
 
            if (gotPacket)
            {
                // запись
                if (av_write_frame(ctx.format, &pkt) < 0)
                {
                    printf("av_write_frame() - failed");
                    exit(1);
                }
            }
 
            av_free_packet(&pkt);
 
        } while (gotPacket);
    }
}
 
//--------------------------------------------------------------------------
int main()
{
    GlobalContext ctx = {};
 
    av_register_all();
 
    InitEncoder(ctx);
    InitFrame(ctx);
    InitResampler(ctx);
    InitFile(ctx, "out.ac3");
 
    // буфер под исходные данные
    float* src_buffer = (float*) malloc(sizeof(float) * channels * sample_cnt);
 
    // основной рабочий цикл
    for (int i = 0; i < 1000; i++)
    {
        GetSourceData(src_buffer, sample_cnt);
        Resample(ctx, src_buffer, sample_cnt);
    }
 
    // выталкивание
    Resample(ctx);
 
    av_write_trailer(ctx.format);
 
    free(src_buffer);
    avformat_free_context(ctx.format);
    swr_free(&ctx.swr);
    av_frame_free(&ctx.frame);
    return 0;
}
1
3 / 3 / 0
Регистрация: 24.02.2017
Сообщений: 140
06.02.2021, 10:50  [ТС]
Ygg, Вы человек ЧЕЛОВЕЧИЩЕ. Спасибо Вам. Пусть в этом году Вам очень сильно повезет)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
06.02.2021, 10:50
Помогаю со студенческими работами здесь

Конвертация файлов через ffmpeg
Всем привет. Возникла такая непонятная проблема. У меня есть папка, в которой лежит конвертор ffmpeg.exe. Тут же лежит exe файл самой...

Массовая конвертация картинок в mp4 (ffmpeg)
Приветствую Ребят помогите в реализации 1) для коммандной строки Есть куча папок c:\pic\папка1; c:\pic\папка5002 и т.д в них...

Av_read_frame и avformat_open_input для аудио по сети (FFMPEG)
Тема про использование библиотеки ffmpeg. Хочу, например, с одного компьютера (с IP = ip1 и port = port1) транслировать аудио поток на...

Конвертация фрейма HEVC в h264 по средствам C API фреймворка ffMpeg
Есть HEVC-поток из IP камеры, нужно получить соответственно H264-поток... Приложение в работе использует фреймворк ffMpeg. Пробовал...

Пакетная конвертация через ffmpeg кодеком xvid в заданный размер не работает
Здравствуйте! Не получается ковертировать кодеком xvid с заданным битрейтом, он его тупо игнорит, хотя с H264 все нормально заработало,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
18
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru