11#include " IMPDeviceSource.hpp"
22#include " GroupsockHelper.hh"
3+ #include < cstring>
34#include < iostream>
45#include < type_traits>
56
7+ static inline int64_t tv_to_us (const struct timeval &tv) {
8+ return static_cast <int64_t >(tv.tv_sec ) * 1000000LL + static_cast <int64_t >(tv.tv_usec );
9+ }
10+
11+ static inline struct timeval us_to_tv (int64_t us) {
12+ struct timeval tv;
13+ tv.tv_sec = static_cast <time_t >(us / 1000000LL );
14+ tv.tv_usec = static_cast <suseconds_t >(us % 1000000LL );
15+ if (tv.tv_usec < 0 ) {
16+ tv.tv_sec -= 1 ;
17+ tv.tv_usec += 1000000 ;
18+ }
19+ return tv;
20+ }
21+
22+ static inline bool is_plausible_wallclock_tv (const struct timeval &tv) {
23+ if (tv.tv_usec < 0 || tv.tv_usec >= 1000000 ) {
24+ return false ;
25+ }
26+ constexpr time_t kMinUnixTime = 946684800 ; // 2000-01-01
27+ return tv.tv_sec >= kMinUnixTime ;
28+ }
29+
630// explicit instantiation
731template class IMPDeviceSource <H264NALUnit, video_stream>;
832template class IMPDeviceSource <AudioFrame, audio_stream>;
@@ -56,7 +80,7 @@ template <typename FrameType, typename Stream> void IMPDeviceSource<FrameType, S
5680 }
5781
5882 FrameType nal;
59- if (stream->msgChannel ->read (&nal)) {
83+ while (stream->msgChannel ->read (&nal)) {
6084 if (nal.data .size () > fMaxSize ) {
6185 fFrameSize = fMaxSize ;
6286 fNumTruncatedBytes = nal.data .size () - fMaxSize ;
@@ -68,15 +92,40 @@ template <typename FrameType, typename Stream> void IMPDeviceSource<FrameType, S
6892 steady +1024 RTP PTS increments and avoid gettimeofday jitter. */
6993 if constexpr (std::is_same_v<FrameType, AudioFrame>) {
7094 auto *imp_audio = global_audio[encChn]->imp_audio ;
71- if (imp_audio && imp_audio->format == IMPAudioFormat::AAC ) {
95+ bool use_aac_clock = false ;
96+ int sampleRate = 16000 ;
97+ if (cfg && cfg->audio .input_sample_rate > 0 ) {
98+ sampleRate = cfg->audio .input_sample_rate ;
99+ }
100+ if (imp_audio) {
101+ use_aac_clock = (imp_audio->format == IMPAudioFormat::AAC );
102+ if (imp_audio->sample_rate > 0 ) {
103+ sampleRate = imp_audio->sample_rate ;
104+ }
105+ } else if (cfg && cfg->audio .input_format && std::strcmp (cfg->audio .input_format , " AAC" ) == 0 ) {
106+ // During transient audio restarts, keep AAC timing behavior stable.
107+ use_aac_clock = true ;
108+ }
109+
110+ bool has_audio_time = is_plausible_wallclock_tv (nal.time );
111+ if (has_audio_time) {
112+ fPresentationTime = nal.time ;
113+ } else if (use_aac_clock) {
72114 if (audioFirstFrame) {
73115 gettimeofday (&audioStartTime, NULL );
74116 audioFrameCount = 0 ;
117+ audioClockSampleRate = sampleRate;
75118 audioFirstFrame = false ;
119+ } else if (audioClockSampleRate <= 0 && sampleRate > 0 ) {
120+ audioClockSampleRate = sampleRate;
121+ }
122+ int effectiveSampleRate = (audioClockSampleRate > 0 ) ? audioClockSampleRate : sampleRate;
123+ if (effectiveSampleRate <= 0 ) {
124+ effectiveSampleRate = 16000 ;
76125 }
77- int sampleRate = (imp_audio->sample_rate > 0 ) ? imp_audio->sample_rate : 16000 ;
78126 constexpr uint64_t kSamplesPerFrame = 1024 ;
79- uint64_t usec_offset = (audioFrameCount * kSamplesPerFrame * 1000000ULL ) / static_cast <uint64_t >(sampleRate);
127+ uint64_t usec_offset =
128+ (audioFrameCount * kSamplesPerFrame * 1000000ULL ) / static_cast <uint64_t >(effectiveSampleRate);
80129 fPresentationTime .tv_sec = audioStartTime.tv_sec + static_cast <time_t >(usec_offset / 1000000ULL );
81130 fPresentationTime .tv_usec = audioStartTime.tv_usec + static_cast <suseconds_t >(usec_offset % 1000000ULL );
82131 if (fPresentationTime .tv_usec >= 1000000 ) {
@@ -87,53 +136,96 @@ template <typename FrameType, typename Stream> void IMPDeviceSource<FrameType, S
87136 } else {
88137 gettimeofday (&fPresentationTime , NULL );
89138 }
90- } else {
91- // Video: use encoder timestamps for stable, jitter-free PTS.
92- // The IMP encoder provides a monotonic microsecond timestamp per NAL.
93- // We anchor it to wall-clock at the first frame, then derive all
94- // subsequent presentation times from the encoder's own clock.
95- if (videoFirstFrame) {
96- gettimeofday (&videoBaseTime, NULL );
97- videoFirstImpTs = nal.imp_ts ;
98- videoFirstFrame = false ;
139+
140+ int64_t pts_us = tv_to_us (fPresentationTime );
141+ int rate_for_tick = sampleRate > 0 ? sampleRate : 16000 ;
142+ int64_t min_audio_step_us = (1000000LL + rate_for_tick - 1 ) / rate_for_tick;
143+ if (use_aac_clock) {
144+ min_audio_step_us = (1024LL * 1000000LL + rate_for_tick - 1 ) / rate_for_tick;
99145 }
100- int64_t delta_us = nal.imp_ts - videoFirstImpTs;
101- // Re-anchor on negative delta (wrap/reset) or any step > 2s relative to
102- // the previous frame — forward (encoder uptime jump) or backward (encoder
103- // counter reset that doesn't go below the anchor).
104- bool needs_reanchor = delta_us < 0 ;
105- if (!needs_reanchor && videoLastDelta >= 0 ) {
106- int64_t step = delta_us - videoLastDelta;
107- needs_reanchor = (step > 2000000LL ) || (step < -500000LL );
146+ if (min_audio_step_us < 1 ) {
147+ min_audio_step_us = 1 ;
108148 }
109- if (needs_reanchor) {
110- gettimeofday (&videoBaseTime, NULL );
111- videoFirstImpTs = nal.imp_ts ;
112- delta_us = 0 ;
113- } else if (videoLastDelta >= 0 && delta_us <= videoLastDelta) {
114- // Clamp small backward jitter (<500ms) to keep PTS strictly
115- // monotonically increasing. The <= also handles the SPS/PPS/IDR
116- // triplet where all three NALs share the same imp_ts (delta_us ==
117- // videoLastDelta): each gets nudged forward by one 90 kHz tick
118- // (≈11 µs) so the RTP sender never emits two packets with the
119- // same timestamp.
120- delta_us = videoLastDelta + 12 ;
149+ if (audioLastPtsUs >= 0 ) {
150+ int64_t delta_pts_us = pts_us - audioLastPtsUs;
151+ int64_t max_forward_jump_us = 1000000LL ;
152+ if (use_aac_clock && min_audio_step_us > 0 ) {
153+ int64_t aac_jump_limit = min_audio_step_us * 120 ;
154+ if (aac_jump_limit > max_forward_jump_us) {
155+ max_forward_jump_us = aac_jump_limit;
156+ }
157+ }
158+ if (delta_pts_us <= 0 || delta_pts_us > max_forward_jump_us) {
159+ pts_us = audioLastPtsUs + min_audio_step_us;
160+ fPresentationTime = us_to_tv (pts_us);
161+ }
121162 }
122- videoLastDelta = delta_us;
123- fPresentationTime .tv_sec = videoBaseTime.tv_sec + static_cast <time_t >(delta_us / 1000000LL );
124- fPresentationTime .tv_usec = videoBaseTime.tv_usec + static_cast <suseconds_t >(delta_us % 1000000LL );
125- if (fPresentationTime .tv_usec >= 1000000 ) {
126- fPresentationTime .tv_sec ++;
127- fPresentationTime .tv_usec -= 1000000 ;
163+ audioLastPtsUs = pts_us;
164+ } else {
165+ if (is_plausible_wallclock_tv (nal.time )) {
166+ fPresentationTime = nal.time ;
167+ } else {
168+ // Video fallback path for sources that don't provide explicit timeval.
169+ int64_t nominal_step_us = 33333 ;
170+ if (stream && stream->stream && stream->stream ->fps > 0 ) {
171+ nominal_step_us = 1000000LL / stream->stream ->fps ;
172+ }
173+ if (nominal_step_us < 12 ) {
174+ nominal_step_us = 12 ;
175+ }
176+ if (videoFirstFrame) {
177+ gettimeofday (&videoBaseTime, NULL );
178+ videoFirstImpTs = nal.imp_ts ;
179+ videoFirstFrame = false ;
180+ }
181+
182+ int64_t delta_us = nal.imp_ts - videoFirstImpTs;
183+
184+ bool needs_reanchor = delta_us < 0 ;
185+ if (!needs_reanchor && videoLastDelta >= 0 ) {
186+ int64_t step = delta_us - videoLastDelta;
187+ needs_reanchor = (step > 2000000LL ) || (step < -500000LL );
188+ }
189+ if (needs_reanchor) {
190+ int64_t target_delta = (videoLastDelta >= 0 ) ? (videoLastDelta + nominal_step_us) : 0 ;
191+ videoFirstImpTs = nal.imp_ts - target_delta;
192+ delta_us = target_delta;
193+ } else if (videoLastDelta >= 0 && delta_us <= videoLastDelta) {
194+ delta_us = videoLastDelta + 12 ;
195+ }
196+ videoLastDelta = delta_us;
197+ fPresentationTime .tv_sec = videoBaseTime.tv_sec + static_cast <time_t >(delta_us / 1000000LL );
198+ fPresentationTime .tv_usec = videoBaseTime.tv_usec + static_cast <suseconds_t >(delta_us % 1000000LL );
199+ if (fPresentationTime .tv_usec >= 1000000 ) {
200+ fPresentationTime .tv_sec ++;
201+ fPresentationTime .tv_usec -= 1000000 ;
202+ }
203+ }
204+
205+ int64_t pts_us = tv_to_us (fPresentationTime );
206+ int64_t min_video_step_us = 12 ;
207+ if (stream && stream->stream && stream->stream ->fps > 0 ) {
208+ min_video_step_us = 1000000LL / stream->stream ->fps ;
209+ if (min_video_step_us < 12 ) {
210+ min_video_step_us = 12 ;
211+ }
212+ }
213+ if (videoLastPtsUs >= 0 ) {
214+ int64_t delta_pts_us = pts_us - videoLastPtsUs;
215+ if (delta_pts_us <= 0 || delta_pts_us > 2000000LL ) {
216+ pts_us = videoLastPtsUs + min_video_step_us;
217+ fPresentationTime = us_to_tv (pts_us);
218+ }
128219 }
220+ videoLastPtsUs = pts_us;
129221 }
130222
131223 memcpy (fTo , &nal.data [0 ], fFrameSize );
132224
133225 if (fFrameSize > 0 ) {
134226 FramedSource::afterGetting (this );
227+ return ;
135228 }
136- } else {
137- fFrameSize = 0 ;
138229 }
230+ fFrameSize = 0 ;
139231}
0 commit comments