尽管输出文件是“光滑”
-
26-10-2019 - |
题
我使用DSPACK组件库在Delphi 6中编写了一个直接投资应用程序。我有两个相互配合的过滤图。
这 基本的 过滤图具有此结构:
- 捕获具有100毫秒缓冲区大小的过滤器。
- (连接到)样品抓取过滤器。
“次级”过滤器图具有此结构。
- 自定义推动源过滤器,该过滤器直接接受音频到IT管理的音频缓冲区仓库。
- (连接到)渲染过滤器。
推源过滤器使用事件来控制音频的传递。它的FillBuffer()命令在事件上等待。当将新的音频数据添加到缓冲区中时,该事件会发出信号。
当我运行过滤器图时,我会在音频中听到微小的“间隙”。通常,我将这种情况与不正确构建的音频缓冲区相关联,这些音频缓冲区未填充或内部“差距”。但是,作为测试,我添加了一个Tee滤波器,并连接了一个WAV DEST滤波器,然后是文件编写器过滤器。当我检查输出WAV文件时,它是完全平滑且连续的。换句话说,我从说话者那里听到的差距在输出文件中并不明显。
这表明虽然捕获过滤器的音频成功地传播,但音频缓冲区的传递正在定期干扰。我听说的“差距”不是每秒10次,而是每秒2或3次,有时根本没有空白。因此,这并不是每个缓冲区都会发生的,否则我会听到一秒钟10次的差距。
我的第一个猜测是这是一个锁定问题,但是我在150毫秒的事件上有一个超时设置,如果发生这种情况,就会抛出异常。没有例外。我也有40毫秒的暂停 每一个 应用程序中使用的关键部分 没有任何 其中也在触发。我检查了我的outputdebugstring()转储以及 未信号 (被阻止)和 发出信号 (未封锁)出现显示了一个相当恒定的模式,在94毫秒至140 ms之间交替。换句话说,在我的推源过滤器中呼叫的FillBuffer()呼叫保持94毫秒,然后是140毫秒,然后重复。请注意持续时间有点漂移,但它很常规。该模式似乎与等待捕获过滤器的线程将其音频缓冲区倒入推送源滤波器的线程间隔100毫秒间隔一致,鉴于Windows线程开关的变体。
我 思考 我在推动源过滤器中使用双重屏障,因此我的信念是,如果没有任何锁定机制的组合时间为200 ms或更多,我不应中断音频流。但是,除了锁定问题会导致这些症状外,我想不出其他任何东西。我在下面的推送源过滤器中包括了我的cancebuffersize()方法中的代码,以防我做错了什么。尽管它有些冗长,但我还包括下面的FillBuffer()调用,以显示我如何生成时间戳,以防可能产生效果。
尽管所有音频缓冲区都完好无损,但还有什么使我的音频流变成渲染过滤器的什么?
问题: :我必须自己实施双重打击吗?我认为DirectShow渲染过滤器会为您做到这一点,否则我创建的其他过滤器图没有自定义推动源过滤器无法正常工作。但是,也许由于我在过滤器图中创建了另一个锁/解锁情况,因此我需要添加自己的双重缓冲层?当然,我想避免这种情况,以避免额外的延迟,因此,如果我想知道的另一个解决方案,我想知道。
function TPushSourcePinBase_wavaudio.DecideBufferSize(Allocator: IMemAllocator; Properties: PAllocatorProperties): HRESULT;
var
// pvi: PVIDEOINFOHEADER;
errMsg: string;
Actual: ALLOCATOR_PROPERTIES;
sampleSize, numBytesPerBuffer: integer;
// ourOwnerFilter: TPushSourceFilterBase_wavaudio;
begin
if (Allocator = nil) or (Properties = nil) then
begin
Result := E_POINTER;
// =========================== EXIT POINT ==============
Exit;
end; // if (Allocator = nil) or (Properties = nil) then
FFilter.StateLock.Lock;
try
// Allocate enough space for the desired amount of milliseconds
// we want to buffer (approximately).
numBytesPerBuffer := Trunc((FOurOwnerFilter.WaveFormatEx.nAvgBytesPerSec / 1000) * FBufferLatencyMS);
// Round it up to be an even multiple of the size of a sample in bytes.
sampleSize := bytesPerSample(FOurOwnerFilter.WaveFormatEx);
// Round it down to the nearest increment of sample size.
numBytesPerBuffer := (numBytesPerBuffer div sampleSize) * sampleSize;
if gDebug then OutputDebugString(PChar(
'(TPushSourcePinBase_wavaudio.DecideBufferSize) Resulting buffer size for audio is: ' + IntToStr(numBytesPerBuffer)
));
// Sanity check on the buffer size.
if numBytesPerBuffer < 1 then
begin
errMsg := '(TPushSourcePinBase_wavaudio.DecideBufferSize) The calculated number of bytes per buffer is zero or less.';
if gDebug then OutputDebugString(PChar(errMsg));
MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);
Result := E_FAIL;
// =========================== EXIT POINT ==============
Exit;
end;
// --------------- Do the buffer allocation -----------------
// Ensure a minimum number of buffers
if (Properties.cBuffers = 0) then
Properties.cBuffers := 2;
Properties.cbBuffer := numBytesPerBuffer;
Result := Allocator.SetProperties(Properties^, Actual);
if Failed(Result) then
// =========================== EXIT POINT ==============
Exit;
// Is this allocator unsuitable?
if (Actual.cbBuffer < Properties.cbBuffer) then
Result := E_FAIL
else
Result := S_OK;
finally
FFilter.StateLock.UnLock;
end; // try()
end;
// *******************************************************
// This is where we provide the audio data.
function TPushSourcePinBase_wavaudio.FillBuffer(Sample: IMediaSample): HResult;
// Given a Wave Format and a Byte count, convert the Byte count
// to a REFERENCE_TIME value.
function byteCountToReferenceTime(waveFormat: TWaveFormat; numBytes: LongInt): REFERENCE_TIME;
var
durationInSeconds: Extended;
begin
if waveFormat.nAvgBytesPerSec <= 0 then
raise Exception.Create('(TPushSourcePinBase_wavaudio.FillBuffer::byteCountToReferenceTime) Invalid average bytes per second value found in the wave format parameter: ' + IntToStr(waveFormat.nAvgBytesPerSec));
// Calculate the duration in seconds given the audio format and the
// number of bytes requested.
durationInSeconds := numBytes / waveFormat.nAvgBytesPerSec;
// Convert it to increments of 100ns since that is the unit value
// for DirectShow timestamps (REFERENCE_TIME).
Result :=
Trunc(durationInSeconds * REFTIME_ONE_SECOND);
end;
// ---------------------------------------------------------------
function min(v1, v2: DWord): DWord;
begin
if v1 <= v2 then
Result := v1
else
Result := v2;
end;
// ---------------------------------------------------------------
var
pData: PByte;
cbData: Longint;
pwfx: PWaveFormat;
aryOutOfDataIDs: TDynamicStringArray;
intfAudFiltNotify: IAudioFilterNotification;
i: integer;
errMsg: string;
bIsShuttingDown: boolean;
// MSDN: The REFERENCE_TIME data type defines the units for reference times
// in DirectShow. Each unit of reference time is 100 nanoseconds.
Start, Stop: REFERENCE_TIME;
durationInRefTime, ofsInRefTime: REFERENCE_TIME;
wfOutputPin: TWaveFormat;
aryDebug: TDynamicByteArray;
begin
aryDebug := nil;
if (Sample = nil) then
begin
Result := E_POINTER;
// =========================== EXIT POINT ==============
Exit;
end; // if (Sample = nil) then
// Quick lock to get sample size.
FSharedState.Lock;
try
cbData := Sample.GetSize;
finally
// Don't want to have our filter state locked when calling
// isEnoughDataOrBlock() since that call can block.
FSharedState.UnLock;
end; // try
aryOutOfDataIDs := nil;
intfAudFiltNotify := nil;
// This call will BLOCK until have enough data to satisfy the request
// or the buffer storage collection is freed.
if FOurOwnerFilter.bufferStorageCollection.isEnoughDataOrBlock(cbData, bIsShuttingDown) then
begin
// If we are shutting down, just exit with S_FALSE as the return to
// tell the caller we are done streaming.
if bIsShuttingDown then
begin
Result := S_FALSE;
// =========================== EXIT POINT ==============
exit;
end; // if bIsShuttingDown then
// Re-acquire the filter state lock.
FSharedState.Lock;
try
// Get the data and return it.
// Access the sample's data buffer
cbData := Sample.GetSize;
Sample.GetPointer(pData);
// Make sure this format matches the media type we are supporting.
pwfx := AMMediaType.pbFormat; // This is the format that our Output pin is set to.
wfOutputPin := waveFormatExToWaveFormat(FOurOwnerFilter.waveFormatEx);
if not isEqualWaveFormat(pwfx^, wfOutputPin) then
begin
Result := E_FAIL;
errMsg :=
'(TPushSourcePinBase_wavaudio.FillBuffer) The wave format of the incoming media sample does not match ours.'
+ CRLF
+ ' > Incoming sample: ' + waveFormatToString(pwfx^)
+ CRLF
+ ' > Our output pin: ' + waveFormatToString(wfOutputPin);
OutputDebugString(PChar(errMsg));
postComponentLogMessage_error(errMsg, FOurOwnerFilter.FFilterName);
MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);
Result := E_FAIL;
// =========================== EXIT POINT ==============
exit;
end; // if not isEqualWaveFormatEx(pwfx^, FOurOwnerFilter.waveFormatEx) then
// Convert the Byte index into the WAV data array into a reference
// time value in order to offset the start and end timestamps.
ofsInRefTime := byteCountToReferenceTime(pwfx^, FWaveByteNdx);
// Convert the number of bytes requested to a reference time vlaue.
durationInRefTime := byteCountToReferenceTime(pwfx^, cbData);
// Now I can calculate the timestamps that will govern the playback
// rate.
Start := ofsInRefTime;
Stop := Start + durationInRefTime;
{
OutputDebugString(PChar(
'(TPushSourcePinBase_wavaudio.FillBuffer) Wave byte index, start time, stop time: '
+ IntToStr(FWaveByteNdx)
+ ', '
+ IntToStr(Start)
+ ', '
+ IntToStr(Stop)
));
}
Sample.SetTime(@Start, @Stop);
// Set TRUE on every sample for uncompressed frames
Sample.SetSyncPoint(True);
// Check that we're still using audio
Assert(IsEqualGUID(AMMediaType.formattype, FORMAT_WaveFormatEx));
{
// Debugging.
FillChar(pData^, cbData, 0);
SetLength(aryDebug, cbData);
if not FOurOwnerFilter.bufferStorageCollection.mixData(@aryDebug[0], cbData, aryOutOfDataIDs) then
}
// Grab the requested number of bytes from the audio data.
if not FOurOwnerFilter.bufferStorageCollection.mixData(pData, cbData, aryOutOfDataIDs) then
begin
// We should not have had any partial copies since we
// called isEnoughDataOrBlock(), which is not supposed to
// return TRUE unless there is enough data.
Result := E_FAIL;
errMsg := '(TPushSourcePinBase_wavaudio.FillBuffer) The mix-data call returned FALSE despite our waiting for sufficient data from all participating buffer channels.';
OutputDebugString(PChar(errMsg));
postComponentLogMessage_error(errMsg, FOurOwnerFilter.FFilterName);
MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);
Result := E_FAIL;
// =========================== EXIT POINT ==============
exit;
end; // if not FOurOwnerFilter.bufferStorageCollection.mixData(pData, cbData, aryOutOfDataIDs) then
// ------------- OUT OF DATA NOTIFICATIONS -----------------
{
WARNING: TBufferStorageCollection automatically posts
AudioFilterNotification messages to any buffer storage
that has a IRequestStep user data interface attached to
it!.
}
if FOurOwnerFilter.wndNotify > 0 then
begin
// ----- Post Audio Notification to Filter level notify handle ---
if Length(aryOutOfDataIDs) > 0 then
begin
for i := Low(aryOutOfDataIDs) to High(aryOutOfDataIDs) do
begin
// Create a notification and post it.
intfAudFiltNotify := TAudioFilterNotification.Create(aryOutOfDataIDs[i], afnOutOfData);
// ourOwnerFilter.intfNotifyRequestStep.triggerResult(intfAudFiltNotify);
PostMessageWithUserDataIntf(FOurOwnerFilter.wndNotify, WM_PUSH_SOURCE_FILTER_NOTIFY, intfAudFiltNotify);
end; // for()
end; // if Length(aryOutOfDataIDs) > 0 then
end; // if FOurOwnerFilter.wndNotify > 0 then
// Advance the Wave Byte index by the number of bytes requested.
Inc(FWaveByteNdx, cbData);
Result := S_OK;
finally
FSharedState.UnLock;
end; // try
end
else
begin
// Tell DirectShow to stop streaming with us. Something has
// gone seriously wrong with the audio streams feeding us.
errMsg := '(TPushSourcePinBase_wavaudio.FillBuffer) Time-out occurred while waiting for sufficient data to accumulate in our audio buffer channels.';
OutputDebugString(PChar(errMsg));
postComponentLogMessage_error(errMsg, FFilter.filterName);
MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);
Result := E_FAIL;
end;
end;
解决方案
首先,要对音频输出进行故障排除,您需要检查渲染器属性。高级标签为您提供这些,您也可以通过 IAMAudioRendererStats
通过编程接口。与文件播放中的属性不同的事物应该为您警告流媒体的正确性。
由于库存过滤器中的音频属性页面不像DriectShow视频过滤器那样坚固,因此您可能需要一个技巧才能弹出。在流媒体处于主动使用时的应用中 OleCreatePropertyFrame
要从GUI线程中直接从代码中显示过滤器proterties(例如,按下一些临时按钮)。
至于典型的播放问题,我将检查以下内容:
- 您没有时间戳样品,而您的播放速度是被推动的速度,有时您会比以前的样本播放完成了一些东西
- 您的时间邮票看起来正确,但是它们是从当前的播放时间恢复的,并且可能部分出现,渲染器迟到了
两种情况都应对 Advanced
选项卡数据。
其他提示
你能尝试改变吗
// Ensure a minimum number of buffers
if (Properties.cBuffers = 0) then
Properties.cBuffers := 2;
进入
// Ensure a minimum number of buffers
if (Properties.cBuffers < 2) then
Properties.cBuffers := 2;
为了确保您至少有两个缓冲区。如果您只有一个缓冲区,您会听到空白。