Obtenir le bégaiement lors du rendu de mon filtre DirectShow malgré le fichier de sortie étant « lisse »
-
26-10-2019 - |
Question
J'ai une application DirectShow écrit en Delphi 6 en utilisant la bibliothèque de composants DSPACK. J'ai deux graphiques de filtre qui coopèrent entre eux.
primaire filtre graphique a cette structure:
- Capture Filter avec 100 ms taille du tampon.
- (connecté à) Un filtre Grabber échantillon.
Le graphique de filtre "secondaire" a cette structure.
- Personnaliser push Source Filtre qui accepte audio directement à un entrepôt, il gère la mémoire tampon audio.
- (connecté à) un rendu filtre.
La poussée Source filtre utilise un événement pour la livraison de commande de l'audio. Ses commande attend FillBuffer () sur l'événement. L'événement est signalé lorsque de nouvelles données audio est ajouté au tampon.
Quand je lance les graphiques de filtre, j'entends minuscules « lacunes » dans l'audio. En général, j'associe cette condition avec un tampon audio mal construits qui ne sont pas remplies ou ont des « lacunes » en eux. Mais comme un test j'ai ajouté un filtre T et connecté un filtre WAV Dest suivi d'un filtre Writer fichier. Lorsque j'examine le fichier WAV de sortie, il est lisse parfaitement et contigu. En d'autres termes, les lacunes que j'entends du haut-parleur ne sont pas évidents dans le fichier de sortie.
Ceci indiquerait que le bien que l'audio du filtre de capture est propageait avec succès, la livraison des tampons audio devient interférence périodique. Les « lacunes » que j'entends ne sont pas 10 fois par seconde, mais plus comme 2 ou 3 fois par seconde, parfois avec de courtes périodes de pas de lacunes du tout. Donc, il ne se produit pas chaque tampon ou j'entendre des trous à 10 fois par seconde.
Ma première hypothèse serait que son problème de verrouillage, mais j'ai un ensemble de temps sur l'événement de 150 ms et si cela se produit une exception est levée. Aucune exception sont jetés. J'ai aussi un temps de 40 ms sur tous Section critique qui est utilisé dans l'application et pas de ceux qui déclenchent soit. Je vérifié mon OutputDebugString () décharges et le temps entre le non signalé (bloqué) et signalée (non bloqué) occurrences montre une alternance de motifs relativement constante entre 94 ms et 140 ms . En d'autres termes, l'appel FillBuffer () dans mon push Source Filtrer les séjours bloqué pendant 94 ms, puis 140 ms, et répète. Notez que les durées dérive un peu, mais son assez régulière. Ce modèle semble compatible avec un thread en attente sur le filtre de capture pour vider sa mémoire tampon audio pour dans la poussée Source filtre sur un intervalle de 100 ms, compte tenu des aléas de la commutation de fil de Windows.
pense J'utilise le double tampon dans ma poussée Source filtre, donc ma conviction est que si aucun des mécanismes de verrouillage prennent un temps combiné de 200 ms ou plus, je ne serais pas interrompre le flux audio. Mais je ne peux pas penser à autre chose que d'un problème de blocage qui cause ces symptômes. J'ai inclus le code de ma méthode DecideBufferSize () dans ma poussée Source filtre ci-dessous au cas où je fais quelque chose de mal. Bien qu'il soit un peu long, je l'ai également inclus l'appel FillBuffer () ci-dessous pour montrer comment je générer horodatages, dans le cas qui pourrait être un effet.
Que pourrait être la cause de mon flux audio au filtre de rendu à bégayer en dépit de tous les tampons audio livré intact?
Question : Dois-je mettre en œuvre une double mémoire tampon moi-même? Je me suis rendu filtres DirectShow faire pour vous, sinon les autres graphiques de filtre I créés sans ma commande push Source Filtre n'aurait pas fonctionné correctement. Mais peut-être que je suis en train de créer une autre situation de verrouillage / déverrouillage dans le filtre graphique, je dois ajouter ma propre couche de double tampon? Je voudrais éviter que bien sûr d'éviter la latence supplémentaire donc s'il y a une autre solution pour ma situation, je voudrais savoir.
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;
La solution
Tout d'abord, pour résoudre la sortie audio que vous voulez vérifier les propriétés renderer. onglets avancés vous obtient ceux-ci et vous pouvez également les interroger via IAMAudioRendererStats
Interface programme. Les choses différentes de propriétés dans la lecture de fichiers devrait être un avertissement pour vous pour l'exactitude de streaming.
Parce que les pages de propriétés audio dans les filtres d'actions ne sont pas faites comme le roc solide que pour les filtres vidéo DriectShow, vous pourriez avoir besoin d'un truc pour faire apparaître cela. Dans votre applciation lors de la diffusion est active utilisation OleCreatePropertyFrame
à protperties filtre show à partir de votre code, de fil GUI (par exemple, comme une réponse à appuyer sur une touche temporaire).
En ce qui concerne les causes typiques des problèmes de lecture, je serais vérifier les éléments suivants:
- Vous n'avez pas des échantillons d'horodatage et Thye sont lus dans le rythme d'être poussé, et vous poussez parfois des choses plus tard que la lecture précédente de l'échantillon est terminé
- Vos timbres de temps semble correct, mais ils sont en retrait de temps de lecture en cours et ils apparaissent, peut-être en partie, en retard pour le moteur de rendu
Les deux scénarios devrait avoir une certaine réflexion sur les données de l'onglet Advanced
.
Autres conseils
Pouvez-vous essayer de changer
// Ensure a minimum number of buffers
if (Properties.cBuffers = 0) then
Properties.cBuffers := 2;
dans
// Ensure a minimum number of buffers
if (Properties.cBuffers < 2) then
Properties.cBuffers := 2;
Pour vous assurer que vous avez au moins deux tampons. Si vous avez un seul tampon vous entendriez des lacunes.