Questo è un bel problema, e non un "dimmi come funziona il codice", ma piuttosto una domanda "come gestisco logicamente questa situazione".Come posso gestire l'errore di arrotondamento progressivo TimeSpan di C# .NET durante la registrazione di video frame per frame?
Ho, in breve, video + audio provenienti da una telecamera IP tramite RTSP.
Il video e l'audio vengono decodificati e registrati fotogramma per fotogramma in un singolo contenitore mp4, tramite thread separati (mostrati di seguito).
Il problema è che il video e l'audio diventano progressivamente più sincronizzati nel tempo, a causa della mancanza di precisione con l'ora di fine TimeSpan e le ore di inizio per ciascun fotogramma video.
Deve essere una durata di 1/framerate = 0.0333667000333667 per ciascun fotogramma video, ma viene utilizzato (anche con il metodo FromTicks()), ora di inizio = 0.0 e ora di fine 0.0333667 per il primo fotogramma.
Sono in grado di regolare il valore del frame rate del decoder video da 29.97 (viene estratto dalle impostazioni della telecamera dichiarate framerate), risultando in un video che precede l'audio o in ritardo rispetto all'audio - questo è semplicemente rendendo ciascun video mediaBuffer .StartTime e mediaBuffer.EndTime o troppo presto o troppo tardi, rispetto all'audio.
Nel corso del tempo, il troncamento decimale minuscola finisce per fare il video e l'audio fuori sincrono - più lunga è la registrazione, il più fuori sincrono le due tracce ottengono.
Non capisco davvero perché ciò stia accadendo, perché l'errore di arrotondamento non dovrebbe essere logicamente rilevante.
Anche se avessi solo una precisione di 1 secondo, avrei solo scritto un fotogramma video al secondo, e il posizionamento nella timeline sarebbe approssimativamente dove dovrebbe essere + - 1 secondo, e questo dovrebbe rendere ogni progressivo inquadra lo stesso + - 1 secondo dove dovrebbe essere, non aggiungendo progressivamente più dislocamento. Sto immaginando che questo sembrerebbe per ogni frame:
[< -------- -1 secondo --------> tempo di frame esatto previsto < ------- - + 1s -------->] ------------------------------------ ---------------- tempo frame registrato --------
Mi manca qualcosa qui?
Non sto facendo "nuovo inizio orario fotogramma = ultimo tempo di fine fotogramma, nuovo tempo di fine fotogramma = nuovo orario di inizio fotogramma + 1/framerate" - In realtà sto facendo "nuovo orario di inizio fotogramma = indice fotogramma - 1/framerate, nuovo frame end time = frame index/framerate ".
Cioè, sto calcolando l'inizio e la fine del frame in base al tempo previsto che dovrebbero avere (frame time = frame position/framerate).
Quello che il mio codice sta facendo è questo:
tempo tempo previsto ---------- ---------- tempo previsto orario previsto arco di tempo cornice di tempo
Capisco matematicamente il problema, ma non capisco perché il troncamento decimale stia provando un tale problema o conosca logicamente quale sia la soluzione migliore per risolverlo.
Se implemento qualcosa che dice "ogni x frame, usa" (1/framerate) + un po 'di importo "per recuperare tutto il tempo mancante, sarà possibile che i frame corrispondano dove dovrebbero essere, o solo risultato in video disordinato?
public void AudioDecoderThreadProc()
{
TimeSpan current = TimeSpan.FromSeconds(0.0);
while (IsRunning)
{
RTPFrame nextFrame = jitter.FindCompleteFrame();
if (nextFrame == null)
{
System.Threading.Thread.Sleep(20);
continue;
}
while (nextFrame.PacketCount > 0 && IsRunning)
{
RTPPacket p = nextFrame.GetNextPacket();
if (sub.ti.MediaCapability.Codec == Codec.G711A || sub.ti.MediaCapability.Codec == Codec.G711U)
{
MediaBuffer<byte> mediaBuffer = new MediaBuffer<byte>(p.DataPointer, 0, (int)p.DataSize);
mediaBuffer.StartTime = current;
mediaBuffer.EndTime = current.Add(TimeSpan.FromSeconds((p.DataSize)/(double)audioDecoder.SampleRate));
current = mediaBuffer.EndTime;
if (SaveToFile == true)
{
WriteMp4Data(mediaBuffer);
}
}
}
}
}
public void VideoDecoderThreadProc()
{
byte[] totalFrame = null;
TimeSpan current = TimeSpan.FromSeconds(0.0);
TimeSpan videoFrame = TimeSpan.FromTicks(3336670);
long frameIndex = 1;
while (IsRunning)
{
if (completedFrames.Count > 50)
{
System.Threading.Thread.Sleep(20);
continue;
}
RTPFrame nextFrame = jitter.FindCompleteFrame();
if (nextFrame == null)
{
System.Threading.Thread.Sleep(20);
continue;
}
if (nextFrame.HasSequenceGaps == true)
{
continue;
}
totalFrame = new byte[nextFrame.TotalPayloadSize * 2];
int offset = 0;
while (nextFrame.PacketCount > 0)
{
byte[] fragFrame = nextFrame.GetAssembledFrame();
if (fragFrame != null)
{
fragFrame.CopyTo(totalFrame, offset);
offset += fragFrame.Length;
}
}
MediaBuffer<byte> mediaBuffer = new MediaBuffer<byte>(
totalFrame,
0,
offset,
TimeSpan.FromTicks(Convert.ToInt64((frameIndex - 1)/mp4TrackInfo.Video.Framerate * 10000000)),
TimeSpan.FromTicks(Convert.ToInt64(frameIndex/mp4TrackInfo.Video.Framerate * 10000000)));
if (SaveToFile == true)
{
WriteMp4Data(mediaBuffer);
}
lock (completedFrames)
{
completedFrames.Add(mediaBuffer);
}
frameIndex++;
}
}
La risoluzione di TimeSpan è di 100 nanosecondi. Quindi, se dovesse essere disattivato in modo coerente, non si potrebbe essere spento dopo un'ora di oltre 100 nsec * 29,97 * 3600 = 11 msec. Non puoi vederlo. Dovrai continuare a cercare. Sfidare il frame rate della telecamera reale. E fai attenzione a un bit rate variabile per l'audio, piuttosto comune. –
Beh, è sicuramente il framerate che è sbagliato, ma come può essere risolto? Guesswork? Posso avvicinarlo, molto più lentamente, o molto più veloce di quanto dovrebbe essere. Ma se non ho un modo per calcolare effettivamente quando è il momento giusto per scrivere quel frame. Come fa qualcuno a fare anche correttamente la sincronizzazione A/V? – user1518816