回复有点晚了,但这是一个我难以解决的问题,其他人可能在同一个地方(即使 OP 早就继续前进了!)。
关键是使用smem插件。这使您可以在播放音频和视频时对其进行跟踪(有一些严重的警告,请参见下文)。为了同时播放和计量,您需要duplicate 流(拆分它)。以下是用于执行此操作的输出字符串:
std::ostringstream outstream;
outstream << ":sout=#duplicate{dst=std{access=file,dst=\"" << filepath << "\"},dst=\"transcode{acodec=s16l}:" << createFrameCallbackString(true) << "\"}";
libvlc_media_add_option(media, outstream.str().c_str());
我们需要创建一个输出链,在其中拆分流,从而调用duplicate。在副本中,有两个目的地。
outstream << ":sout=#duplicate{dst=...,dst=...}";
在我的示例中,我正在录制到文件(而不是显示到视频窗口),因此您希望将第一个目标替换为您自己的。
第二个目的地是我们使用smem 将音频(和/或视频)发送到回调方法的地方:
"..., dst=\"transcode{acodec=s16l}:" << createFrameCallbackString(true) << "\"}"
注意 smem 中的音频,它必须在 PCM 中(或者文档中似乎如此),因此我们将其转码为带符号的 16 位小端 (s16l)(最后是 L,而不是 1 )。
转码的间接费用是多少?在我的测试中,它似乎很小,但如果您有性能问题,值得一看。
为smem配置输出链如下:
class Player
{
//callback for frame monitoring
static void prerendercb(void *data, unsigned char** buffer, size_t size)
{
//we must allocate a buffer in order for postrender to be called
*buffer = (uint8_t *)malloc(size);
}
static void postrendercb(void *data, unsigned char* buffer, int width, int height, int pitch, size_t size, int64_t pts)
{
Player* context = (Player*)data;
//free the buffer
free(buffer);
//notify context of frame for processing
context->OnFrame(/*pass it whatever information you need*/);
}
static void audio_prerendercb(void* data, unsigned char** buffer, size_t size)
{
*buffer = (uint8_t *)malloc(size);
}
static void audio_postrendercb(void* data, unsigned char* buffer, unsigned int channels, unsigned int rate,
unsigned int nb_samples, unsigned int bits_per_sample, size_t size, int64_t pts)
{
Player* context = (Player*)data;
//free the buffer
free(buffer);
//notify context
context->OnAudioFrame(/*whatever data is needed*/);
}
//Get an smem string for the audio/video callbacks
std::string Player::createFrameCallbackString(bool timesync) const
{
std::ostringstream text;
text <<
"smem{" << "video-prerender-callback=" << ((long long int)(intptr_t)(void*)&prerendercb) << "," <<
"video-postrender-callback=" << ((long long int)(intptr_t)(void*)&postrendercb) << "," <<
"video-data=" << ((long long int)(intptr_t)(void*)this) << "," <<
"audio-prerender-callback=" << ((long long int)(intptr_t)(void*)&audio_prerendercb) << "," <<
"audio-postrender-callback=" << ((long long int)(intptr_t)(void*)&audio_postrendercb) << "," <<
"audio-data=" << ((long long int)(intptr_t)(void*)this) << "," <<
"time-sync=" << (timesync ? "1" : "0") <<
"}";
return text.str();
}
}
注意事项
除了转码问题以及不管你是否关心都必须分配缓冲区数据这一事实之外,最大的问题是 libVLC异步处理帧。结果,您将倾向于获得一次处理 10、15 帧的突发,然后您将有一段时间看不到任何内容,然后是另一个突发。这肯定会受到缓存大小的影响(libVLC 中有几个),但即使将缓存大小设置得尽可能低,您仍然会得到至少 2 或 3 帧的突发。
设置这些选项很有帮助:
libvlc_media_add_option(media, ":clock-synchro=0"); //this setting is critical for realtime processing of audio stream!
libvlc_media_add_option(media, ":clock-jitter=0");
构建输出链时的另一个警告是注意引号和括号。可能在某处发布了指导方针,但对我来说这是一个反复试验。
最后,在smem 中使用time-sync 选项时,我从未注意到合理的区别。我确信它的设置很重要,但我无法确定如何设置。
我在使用 libVLC 时发现它并不是真正为这种用途而设计的。它可以被扭曲成可用的形状,但它的性能永远不会达到应有的水平。虽然它是开源的,但喜欢冒险的人也许可以通过修改源代码来制定更有效的解决方案。