Я декодирую видео OGG (theora & vorbis в качестве кодеков) и хотите показать его на экране (используя Ogre 3D) во время воспроизведения звука. Я могу отлично декодировать поток изображений, и видео воспроизводится с правильной частотой кадров и т. Д.
Тем не менее, я не могу получить звук для воспроизведения с OpenAL.
Редактировать: Мне удалось сделать звучание, похожее на реальный звук в видео, хотя бы несколько. Обновлен пример кода.
Изменить 2: Теперь я смог получить «почти» правильный звук. Я должен был установить OpenAL для использования AL_FORMAT_STEREO_FLOAT32 (после инициализации расширения) вместо просто STEREO16. Теперь звук «только» чрезвычайно высокочастотный и заикающийся, но с правильной скоростью.
Вот как я декодирую аудиопакеты (в фоновом потоке эквивалент отлично работает для потока изображений видеофайла):
//------------------------------------------------------------------------------
int decodeAudioPacket( AVPacket& p_packet, AVCodecContext* p_audioCodecContext, AVFrame* p_frame,
FFmpegVideoPlayer* p_player, VideoInfo& p_videoInfo)
{
// Decode audio frame
int got_frame = 0;
int decoded = avcodec_decode_audio4(p_audioCodecContext, p_frame, &got_frame, &p_packet);
if (decoded < 0)
{
p_videoInfo.error = "Error decoding audio frame.";
return decoded;
}
// Frame is complete, store it in audio frame queue
if (got_frame)
{
int bufferSize = av_samples_get_buffer_size(NULL, p_audioCodecContext->channels, p_frame->nb_samples,
p_audioCodecContext->sample_fmt, 0);
int64_t duration = p_frame->pkt_duration;
int64_t dts = p_frame->pkt_dts;
if (staticOgreLog)
{
staticOgreLog->logMessage("Audio frame bufferSize / duration / dts: "+ boost::lexical_cast<std::string>(bufferSize) + " / "+ boost::lexical_cast<std::string>(duration) + " / "+ boost::lexical_cast<std::string>(dts), Ogre::LML_NORMAL);
}
// Create the audio frame
AudioFrame* frame = new AudioFrame();
frame->dataSize = bufferSize;
frame->data = new uint8_t[bufferSize];
if (p_frame->channels == 2)
{
memcpy(frame->data, p_frame->data[0], bufferSize >> 1);
memcpy(frame->data + (bufferSize >> 1), p_frame->data[1], bufferSize >> 1);
}
else
{
memcpy(frame->data, p_frame->data, bufferSize);
}
double timeBase = ((double)p_audioCodecContext->time_base.num) / (double)p_audioCodecContext->time_base.den;
frame->lifeTime = duration * timeBase;
p_player->addAudioFrame(frame);
}
return decoded;
}
Итак, как вы можете видеть, я декодирую фрейм, записывая его в мою собственную структуру AudioFrame. Теперь, когда звук воспроизводится, я использую эти аудио кадры, как это:
int numBuffers = 4;
ALuint buffers[4];
alGenBuffers(numBuffers, buffers);
ALenum success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error on alGenBuffers : " + Ogre::StringConverter::toString(success) + alGetString(success));
return;
}
// Fill a number of data buffers with audio from the stream
std::vector<AudioFrame*> audioBuffers;
std::vector<unsigned int> audioBufferSizes;
unsigned int numReturned = FFMPEG_PLAYER->getDecodedAudioFrames(numBuffers, audioBuffers, audioBufferSizes);
// Assign the data buffers to the OpenAL buffers
for (unsigned int i = 0; i < numReturned; ++i)
{
alBufferData(buffers[i], _streamingFormat, audioBuffers[i]->data, audioBufferSizes[i], _streamingFrequency);
success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error on alBufferData : " + Ogre::StringConverter::toString(success) + alGetString(success)
+ " size: " + Ogre::StringConverter::toString(audioBufferSizes[i]));
return;
}
}
// Queue the buffers into OpenAL
alSourceQueueBuffers(_source, numReturned, buffers);
success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error queuing streaming buffers: " + Ogre::StringConverter::toString(success) + alGetString(success));
return;
}
}
alSourcePlay(_source);
Формат и частота, которые я даю OpenAL: AL_FORMAT_STEREO_FLOAT32 (это стереофонический звуковой поток, и я действительно инициализировал расширение FLOAT32) и 48000 (это частота дискретизации AVCodecContext аудиопотока).
И во время воспроизведения я делаю следующее, чтобы пополнить буферы OpenAL:
ALint numBuffersProcessed;
// Check if OpenAL is done with any of the queued buffers
alGetSourcei(_source, AL_BUFFERS_PROCESSED, &numBuffersProcessed);
if(numBuffersProcessed <= 0)
return;
// Fill a number of data buffers with audio from the stream
std::vector<AudiFrame*> audioBuffers;
std::vector<unsigned int> audioBufferSizes;
unsigned int numFilled = FFMPEG_PLAYER->getDecodedAudioFrames(numBuffersProcessed, audioBuffers, audioBufferSizes);
// Assign the data buffers to the OpenAL buffers
ALuint buffer;
for (unsigned int i = 0; i < numFilled; ++i)
{
// Pop the oldest queued buffer from the source,
// fill it with the new data, then re-queue it
alSourceUnqueueBuffers(_source, 1, &buffer);
ALenum success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error Unqueuing streaming buffers: " + Ogre::StringConverter::toString(success));
return;
}
alBufferData(buffer, _streamingFormat, audioBuffers[i]->data, audioBufferSizes[i], _streamingFrequency);
success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error on re- alBufferData: " + Ogre::StringConverter::toString(success));
return;
}
alSourceQueueBuffers(_source, 1, &buffer);
success = alGetError();
if(success != AL_NO_ERROR)
{
CONSOLE_LOG("Error re-queuing streaming buffers: " + Ogre::StringConverter::toString(success) + " "+ alGetString(success));
return;
}
}
// Make sure the source is still playing,
// and restart it if needed.
ALint playStatus;
alGetSourcei(_source, AL_SOURCE_STATE, &playStatus);
if(playStatus != AL_PLAYING)
alSourcePlay(_source);
Как видите, я делаю довольно серьезную проверку ошибок. Но я не получаю никаких ошибок, ни от OpenAL, ни от FFmpeg.
Редактировать: То, что я слышу, немного напоминает реальный звук из видео, но ОЧЕНЬ высокий и ОЧЕНЬ сильно заикающийся. Кроме того, кажется, что он играет поверх телевизионного шума. Очень странно. Кроме того, он воспроизводится гораздо медленнее, чем правильный звук.
Редактировать: 2 После использования AL_FORMAT_STEREO_FLOAT32 звук воспроизводится с правильной скоростью, но все еще остается очень высоким и заикающимся (хотя и меньше, чем раньше).
Само видео не битое, его можно нормально воспроизвести на любом плеере. OpenAL также может нормально воспроизводить файлы * .way в одном приложении, поэтому он также работает.
Есть идеи, что здесь может быть не так или как это сделать правильно?
Мое единственное предположение, что функция декодирования FFmpeg не дает данных, которые OpenGL может прочитать. Но это касается примера декодирования FFmpeg, поэтому я не знаю, чего не хватает. Насколько я понимаю, функция decode_audio4 декодирует кадр в необработанные данные. И OpenAL должен уметь работать с RAW-данными (или, скорее, не работать ни с чем другим).
Итак, я наконец-то понял, как это сделать. Ну и дела, что за беспорядок. Это был намек от пользователя из списка рассылки libav-users, который указал мне правильный путь.
Вот мои ошибки:
Использование неправильного формата в функции alBufferData. Я использовал AL_FORMAT_STEREO16 (поскольку это то, что использует каждый отдельный пример потоковой передачи с OpenAL). Я должен был использовать AL_FORMAT_STEREO_FLOAT32, так как видео, которое я передаю, является Ogg, а vorbis хранится в плавающих точках. А использование swr_convert для преобразования из AV_SAMPLE_FMT_FLTP в AV_SAMPLE_FMT_S16 просто дает сбой. Понятия не имею почему.
Не использовать swr_convert для преобразования декодированного аудиокадра в целевой формат. После того, как я попытался использовать swr_convert для преобразования из FLTP в S16, и он просто потерпел крах без объяснения причин, я предположил, что он сломался. Но после выяснения моей первой ошибки я попытался снова, преобразовав FLTP в FLT (непланарный), и тогда это сработало! Так что OpenAL использует чередующийся формат, а не планарный. Хорошо знать.
Итак, вот функция decodeAudioPacket, которая работает для меня с видео Ogg, аудио поток vorbis:
int decodeAudioPacket( AVPacket& p_packet, AVCodecContext* p_audioCodecContext, AVFrame* p_frame,
SwrContext* p_swrContext, uint8_t** p_destBuffer, int p_destLinesize,
FFmpegVideoPlayer* p_player, VideoInfo& p_videoInfo)
{
// Decode audio frame
int got_frame = 0;
int decoded = avcodec_decode_audio4(p_audioCodecContext, p_frame, &got_frame, &p_packet);
if (decoded < 0)
{
p_videoInfo.error = "Error decoding audio frame.";
return decoded;
}
if(decoded <= p_packet.size)
{
/* Move the unread data to the front and clear the end bits */
int remaining = p_packet.size - decoded;
memmove(p_packet.data, &p_packet.data[decoded], remaining);
av_shrink_packet(&p_packet, remaining);
}
// Frame is complete, store it in audio frame queue
if (got_frame)
{
int outputSamples = swr_convert(p_swrContext,
p_destBuffer, p_destLinesize,
(const uint8_t**)p_frame->extended_data, p_frame->nb_samples);
int bufferSize = av_get_bytes_per_sample(AV_SAMPLE_FMT_FLT) * p_videoInfo.audioNumChannels
* outputSamples;
int64_t duration = p_frame->pkt_duration;
int64_t dts = p_frame->pkt_dts;
if (staticOgreLog)
{
staticOgreLog->logMessage("Audio frame bufferSize / duration / dts: "+ boost::lexical_cast<std::string>(bufferSize) + " / "+ boost::lexical_cast<std::string>(duration) + " / "+ boost::lexical_cast<std::string>(dts), Ogre::LML_NORMAL);
}
// Create the audio frame
AudioFrame* frame = new AudioFrame();
frame->dataSize = bufferSize;
frame->data = new uint8_t[bufferSize];
memcpy(frame->data, p_destBuffer[0], bufferSize);
double timeBase = ((double)p_audioCodecContext->time_base.num) / (double)p_audioCodecContext->time_base.den;
frame->lifeTime = duration * timeBase;
p_player->addAudioFrame(frame);
}
return decoded;
}
И вот как я инициализирую контекст и буфер назначения:
// Initialize SWR context
SwrContext* swrContext = swr_alloc_set_opts(NULL,
audioCodecContext->channel_layout, AV_SAMPLE_FMT_FLT, audioCodecContext->sample_rate,
audioCodecContext->channel_layout, audioCodecContext->sample_fmt, audioCodecContext->sample_rate,
0, NULL);
int result = swr_init(swrContext);
// Create destination sample buffer
uint8_t** destBuffer = NULL;
int destBufferLinesize;
av_samples_alloc_array_and_samples( &destBuffer,
&destBufferLinesize,
videoInfo.audioNumChannels,
2048,
AV_SAMPLE_FMT_FLT,
0);
Других решений пока нет …