diff --git a/app/src/main/cpp/rtsp_streamer.cpp b/app/src/main/cpp/rtsp_streamer.cpp index 17f5558..d8f2d94 100644 --- a/app/src/main/cpp/rtsp_streamer.cpp +++ b/app/src/main/cpp/rtsp_streamer.cpp @@ -63,9 +63,16 @@ bool RtspStreamer::initialize(const std::string& url, int width, int height, int codecpar->format = AV_PIX_FMT_YUV420P; codecpar->bit_rate = 2000000; // 2 Mbps - // Stream timebase (90kHz is standard for H.264 in RTSP) + // Set stream timebase (90kHz is standard for H.264 in RTSP) mVideoStream->time_base = (AVRational){1, 90000}; + // Set additional RTSP parameters + av_dict_set(&mFormatCtx->metadata, "rtsp_transport", "tcp", 0); + + // Specifically configure FFmpeg to handle timestamps properly + // Disable FFmpeg's internal timestamp generation + mFormatCtx->flags |= AVFMT_FLAG_GENPTS; + // Open output URL if (!(mFormatCtx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&mFormatCtx->pb, url.c_str(), AVIO_FLAG_WRITE); @@ -178,9 +185,16 @@ void RtspStreamer::stop() { } } +// Modify the streamLoop method to handle timestamps properly: void RtspStreamer::streamLoop() { bool firstFrame = true; int64_t firstPts = 0; + int64_t lastPts = 0; + uint32_t rtpTimestamp = 0; + + // Frame duration in RTP clock units (90kHz for H.264) + // For example, at 30 fps: 90000/30 = 3000 units per frame + const int frameRateRtp = 90000 / 30; // Adjust 30 to match your actual fps while (mRunning) { EncodedFrame frame; @@ -204,47 +218,68 @@ void RtspStreamer::streamLoop() { // Reset the packet av_packet_unref(mPacket); - // Save first timestamp for offset calculation + // For timestamp handling if (firstFrame) { firstPts = frame.presentationTimeUs; + lastPts = firstPts; + rtpTimestamp = 0; // Start at 0 firstFrame = false; } - // Create a copy of the frame data that FFmpeg will manage + // Calculate pts based on frame number rather than actual timestamps + // to ensure smooth, consistent timing + int64_t expectedPts = 0; + + if (frame.presentationTimeUs - lastPts > 1000000) { // Gap larger than 1 second + // There's been a significant gap - adjust smoothly + __android_log_print(ANDROID_LOG_WARN, "RtspStreamer", + "Large timestamp gap detected: %lld -> %lld, smoothing", + (long long)lastPts, (long long)frame.presentationTimeUs); + + // Just increment by normal frame duration instead of actual gap + rtpTimestamp += frameRateRtp; + } else { + // Calculate proper increment based on time diff, but limit max increase + int64_t diffUs = frame.presentationTimeUs - lastPts; + int32_t rtpIncrement = (diffUs * 90) / 1000; // Convert μs to 90kHz units + + // Limit maximum increment to avoid large jumps + if (rtpIncrement > frameRateRtp * 3) { + __android_log_print(ANDROID_LOG_WARN, "RtspStreamer", + "Limiting large timestamp increment: %d", rtpIncrement); + rtpIncrement = frameRateRtp; + } + + rtpTimestamp += rtpIncrement; + } + + lastPts = frame.presentationTimeUs; + + // Create packet from frame data uint8_t* buffer = (uint8_t*)av_malloc(frame.size); if (!buffer) { - LOGE("Failed to allocate buffer for frame"); - delete[] frame.data; // Free our copy + __android_log_print(ANDROID_LOG_ERROR, "RtspStreamer", "Failed to allocate buffer"); + delete[] frame.data; continue; } - // Copy frame data to the FFmpeg-managed buffer + // Copy data to FFmpeg-managed buffer memcpy(buffer, frame.data, frame.size); + delete[] frame.data; // Free our copy + frame.data = nullptr; - // We can now free our copy of the data - delete[] frame.data; - frame.data = nullptr; // Avoid accidental double-delete - - // Let FFmpeg manage the buffer int ret = av_packet_from_data(mPacket, buffer, frame.size); if (ret < 0) { - LOGE("Failed to create packet from data: %d", ret); - av_free(buffer); // Free FFmpeg buffer on error + __android_log_print(ANDROID_LOG_ERROR, "RtspStreamer", + "Failed to create packet: %d", ret); + av_free(buffer); continue; } - // Now mPacket owns the buffer, we don't need to free it manually - - // Offset timestamp by first frame for proper timing - int64_t pts = frame.presentationTimeUs - firstPts; - - // Convert to stream timebase (90kHz) - pts = av_rescale_q(pts, (AVRational){1, 1000000}, mVideoStream->time_base); - - // Set packet properties - mPacket->pts = pts; - mPacket->dts = pts; - mPacket->duration = 0; + // Set packet properties with our smoothed timestamp + mPacket->pts = rtpTimestamp; + mPacket->dts = rtpTimestamp; + mPacket->duration = frameRateRtp; // Set proper duration mPacket->flags = frame.isKeyFrame ? AV_PKT_FLAG_KEY : 0; mPacket->stream_index = mVideoStream->index; @@ -253,13 +288,11 @@ void RtspStreamer::streamLoop() { if (ret < 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0}; av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); - LOGE("Error writing frame: %d (%s)", ret, errbuf); + __android_log_print(ANDROID_LOG_ERROR, "RtspStreamer", + "Error writing frame: %d (%s)", ret, errbuf); - // Handle reconnection logic as before... + // Handle reconnection logic... } - - // We don't need to delete frame.data here anymore - it's already been freed above - // and ownership of the buffer has been transferred to FFmpeg } } -} \ No newline at end of file +}