BOOL OSWaitRendezvousWithTimeout(OSRendezvous *rendezvous, uint32_t coreMask, OSTime timeout) { auto core = OSGetCoreId(); auto success = FALSE; auto endTime = OSGetTime() + timeout; // Set our core flag rendezvous->core[core].store(1, std::memory_order_release); do { success = TRUE; // Check all core flags for (auto i = 0u; i < 3; ++i) { if (coreMask & (1 << i)) { if (!rendezvous->core[i].load(std::memory_order_acquire)) { success = FALSE; } } } // Check for timeout if (timeout != -1 && OSGetTime() >= endTime) { break; } } while (!success); return success; }
~DelayedPublisher() { if(!bStopping) { App->EnableSceneSwitching(FALSE); EnableWindow (hwndMain, FALSE); bStreamEnding = true; HWND hwndProgressDialog = OBSCreateDialog(hinstMain, MAKEINTRESOURCE(IDD_ENDINGDELAY), hwndMain, (DLGPROC)EndDelayProc, (LPARAM)this); ProcessEvents(); ShowWindow(hwndProgressDialog, TRUE); DWORD totalTimeLeft = delayTime; String strTimeLeftVal = Str("EndingDelay.TimeLeft"); DWORD lastTimeLeft = -1; DWORD firstTime = OSGetTime(); while(delayedPackets.Num() && !bCancelEnd) { ProcessEvents(); DWORD timeElapsed = (OSGetTime()-firstTime); DWORD timeLeft = (totalTimeLeft-timeElapsed)/1000; DWORD timeLeftMinutes = timeLeft/60; DWORD timeLeftSeconds = timeLeft%60; if((timeLeft != lastTimeLeft) && (totalTimeLeft >= timeElapsed)) { String strTimeLeft = strTimeLeftVal; strTimeLeft.FindReplace(TEXT("$1"), FormattedString(TEXT("%u:%02u"), timeLeftMinutes, timeLeftSeconds)); SetWindowText(GetDlgItem(hwndProgressDialog, IDC_TIMELEFT), strTimeLeft); lastTimeLeft = timeLeft; } ProcessDelayedPackets(lastTimestamp+timeElapsed); if(bStopping) bCancelEnd = true; Sleep(10); } EnableWindow (hwndMain, TRUE); App->EnableSceneSwitching(TRUE); DestroyWindow(hwndProgressDialog); } for(UINT i=0; i<delayedPackets.Num(); i++) delayedPackets[i].data.Clear(); }
void OSCheckAlarms(uint32_t core, OSContext *context) { ScopedSpinLock lock(gAlarmLock); auto queue = gAlarmQueue[core]; auto now = OSGetTime(); auto next = std::chrono::time_point<std::chrono::system_clock>::max(); for (OSAlarm *alarm = queue->head; alarm; ) { auto nextAlarm = alarm->link.next; // Trigger alarm if it is time if (alarm->nextFire <= now) { OSTriggerAlarmNoLock(alarm, context); } // Set next timer if alarm is set if (alarm->state == OSAlarmState::Set && alarm->nextFire) { auto nextFire = OSTimeToChrono(alarm->nextFire); if (nextFire < next) { next = nextFire; } } alarm = nextAlarm; } gProcessor.setInterruptTimer(core, next); }
static int32_t syscall_time() { auto t = internal::toTimepoint(OSGetTime()); auto time = t.time_since_epoch() / std::chrono::seconds(1); return static_cast<int32_t>(time); }
/** * Busy wait with timeout until state matches mask. * * \return Returns FALSE if wait timed out. */ BOOL MPWaitTaskQWithTimeout(virt_ptr<MPTaskQueue> queue, MPTaskQueueState mask, OSTimeNanoseconds timeoutNS) { auto start = OSGetTime(); auto end = start + internal::nsToTicks(timeoutNS); while ((queue->state & mask) == 0) { if (OSGetTime() >= end) { break; } } return (queue->state & mask) != 0; }
void queueCommandBuffer(pm4::Buffer *buf) { buf->submitTime = OSGetTime(); gx2::internal::setLastSubmittedTimestamp(buf->submitTime); gQueue.appendBuffer(buf); }
/* Basic Cafe OS clock thingy. */ int _gettimeofday_r(struct _reent *ptr, struct timeval* ptimeval, void* ptimezone) { OSTime cosTime; uint64_t cosSecs; uint32_t cosUSecs; time_t unixSecs; /* We need somewhere to put our output */ if (ptimeval == NULL) { errno = EFAULT; return -1; } /* Get Cafe OS clock in seconds; epoch 2000-01-01 00:00 */ cosTime = OSGetTime(); cosSecs = ticks_to_sec(cosTime); /* Get extra milliseconds */ cosUSecs = ticks_to_us(cosTime) - (cosSecs * 1000000); /* Convert to Unix time, epoch 1970-01-01 00:00. Constant value is seconds between 1970 and 2000. time_t is 32bit here, so the Wii U is vulnerable to the 2038 problem. */ unixSecs = cosSecs + 946684800; ptimeval->tv_sec = unixSecs; ptimeval->tv_usec = cosUSecs; return 0; }
/** * Busy wait with timeout until state matches mask. * * \return Returns FALSE if wait timed out. */ BOOL MPWaitTaskQWithTimeout(MPTaskQueue *queue, MPTaskQueueState mask, OSTime timeout) { auto start = OSGetTime(); auto end = start + timeout; while ((queue->state & mask) == 0) { if (OSGetTime() >= end) { break; } } return (queue->state & mask) != 0; }
/** * Set a one shot alarm to perform a callback after an amount of time. */ BOOL OSSetAlarm(OSAlarm *alarm, OSTime time, AlarmCallback callback) { return OSSetPeriodicAlarm(alarm, OSGetTime() + time, 0, callback); }
/** * Run a specific task. * * The task must belong to a queue. * The task must be in the Ready state. * * \return Returns TRUE if task was run. */ BOOL MPRunTask(virt_ptr<MPTask> task) { auto queue = task->queue; if (task->state != MPTaskState::Ready) { return FALSE; } if (!queue || queue->state == MPTaskQueueState::Stopping || queue->state == MPTaskQueueState::Stopped) { return FALSE; } OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); queue->tasksReady--; queue->tasksRunning++; OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); task->state = MPTaskState::Running; task->coreID = OSGetCoreId(); auto start = OSGetTime(); task->result = cafe::invoke(cpu::this_core::state(), task->func, task->userArg1, task->userArg2); task->duration = OSGetTime() - start; task->state = MPTaskState::Finished; OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); queue->tasksRunning--; queue->tasksFinished++; if (queue->state == MPTaskQueueState::Stopping && queue->tasksRunning == 0) { queue->state = MPTaskQueueState::Stopped; } if (queue->tasks == queue->tasksFinished) { queue->state = MPTaskQueueState::Finished; } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; }
/** * Wait on a rendezvous with a timeout. * * This will wait with a timeout until all cores matching coreMask have * reached the rendezvous point. * * \return Returns TRUE on success, FALSE on timeout. */ BOOL OSWaitRendezvousWithTimeout(OSRendezvous *rendezvous, uint32_t coreMask, OSTime timeoutNS) { auto core = OSGetCoreId(); auto success = FALSE; auto endTime = OSGetTime() + internal::nanosToTicks(timeoutNS); auto waitCore0 = (coreMask & (1 << 0)) != 0; auto waitCore1 = (coreMask & (1 << 1)) != 0; auto waitCore2 = (coreMask & (1 << 2)) != 0; // Set our core flag rendezvous->core[core].store(1, std::memory_order_release); do { if (waitCore0 && rendezvous->core[0].load(std::memory_order_acquire)) { waitCore0 = false; } if (waitCore1 && rendezvous->core[1].load(std::memory_order_acquire)) { waitCore1 = false; } if (waitCore2 && rendezvous->core[2].load(std::memory_order_acquire)) { waitCore2 = false; } if (!waitCore0 && !waitCore1 && !waitCore2) { success = TRUE; break; } if (timeoutNS != -1 && OSGetTime() >= endTime) { break; } // We must manually check for interrupts here, as we are busy-looping. // Note that this is only safe as no locks are held during the wait. cpu::this_core::checkInterrupts(); } while (true); return success; }
void handleAlarmInterrupt(OSContext *context) { auto core_id = cpu::this_core::id(); auto queue = sAlarmQueue[core_id]; auto cbQueue = sAlarmCallbackQueue[core_id]; auto cbThreadQueue = sAlarmCallbackThreadQueue[core_id]; auto now = OSGetTime(); auto next = std::chrono::time_point<std::chrono::system_clock>::max(); bool callbacksNeeded = false; internal::lockScheduler(); acquireIdLock(sAlarmLock); for (OSAlarm *alarm = queue->head; alarm; ) { auto nextAlarm = alarm->link.next; // Expire it if its past its nextFire time if (alarm->nextFire <= now) { decaf_check(alarm->state == OSAlarmState::Set); internal::AlarmQueue::erase(queue, alarm); alarm->alarmQueue = nullptr; alarm->state = OSAlarmState::Expired; alarm->context = context; if (alarm->threadQueue.head) { wakeupThreadNoLock(&alarm->threadQueue); rescheduleOtherCoreNoLock(); } if (alarm->group == 0xFFFFFFFF) { // System-internal alarm if (alarm->callback) { auto originalMask = cpu::this_core::setInterruptMask(0); alarm->callback(alarm, context); cpu::this_core::setInterruptMask(originalMask); } } else { internal::AlarmQueue::append(cbQueue, alarm); alarm->alarmQueue = cbQueue; wakeupThreadNoLock(cbThreadQueue); } } alarm = nextAlarm; } internal::updateCpuAlarmNoALock(); releaseIdLock(sAlarmLock); internal::unlockScheduler(); }
void _GX2InitVsync() { gVsyncThreadQueue = OSAllocFromSystem<OSThreadQueue>(); gVsyncAlarm = OSAllocFromSystem<OSAlarm>(); auto ticks = (static_cast<OSTime>(OSGetSystemInfo()->clockSpeed / 4) * 60) / 1000; OSCreateAlarm(gVsyncAlarm); OSSetPeriodicAlarm(gVsyncAlarm, 0, OSGetTime(), ticks, pVsyncAlarmHandler); }
time_t gsiTimeInSec(time_t *timer) { time_t t = 0; t = (gsi_i32)OSTicksToSeconds(OSGetTime()); if (timer) *timer = t; return t; }
void Module::initialiseSystemInformation() { sSystemInfo = coreinit::internal::sysAlloc<OSSystemInfo>(); // Clockspeed is 4 * 1 second in ticks auto oneSecond = std::chrono::seconds(1); auto oneSecondNS = std::chrono::duration_cast<std::chrono::nanoseconds>(oneSecond); sSystemInfo->clockSpeed = oneSecondNS.count() * 4; // Set startup time sSystemInfo->baseTime = OSGetTime(); sScreenCapturePermission = TRUE; sEnableHomeButtonMenu = TRUE; }
void SendPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type) { DWORD curTime = OSGetTime(); curBytes += size+8; //just assume a header of 8 bytes if((curTime-lastTime) > 1000) { totalBytesTransmitted += curBytes; numSeconds++; if(curBytes > highestBytes) highestBytes = curBytes; curBytes = 0; lastTime += 1000; } }
void RTMPPublisher::ProcessPackets() { if(!bStreamStarted && !bStopping) { BeginPublishingInternal(); bStreamStarted = true; } //never drop frames if we're in the shutdown sequence, just wait it out if (!bStopping) { if (queuedPackets.Num() && minFramedropTimestsamp < queuedPackets[0].timestamp) { DWORD queueDuration = (queuedPackets.Last().timestamp - queuedPackets[0].timestamp); DWORD curTime = OSGetTime(); if (queueDuration >= dropThreshold + audioTimeOffset) { minFramedropTimestsamp = queuedPackets.Last().timestamp; OSDebugOut(TEXT("dropped all at %u, threshold is %u, total duration is %u, %d in queue\r\n"), currentBufferSize, dropThreshold + audioTimeOffset, queueDuration, queuedPackets.Num()); //what the hell, just flush it all for now as a test and force a keyframe 1 second after while (DoIFrameDelay(false)); if(packetWaitType > PacketType_VideoLow) RequestKeyframe(1000); } else if (queueDuration >= bframeDropThreshold + audioTimeOffset && curTime-lastBFrameDropTime >= dropThreshold + audioTimeOffset) { OSDebugOut(TEXT("dropped b-frames at %u, threshold is %u, total duration is %u\r\n"), currentBufferSize, bframeDropThreshold + audioTimeOffset, queueDuration); while (DoIFrameDelay(true)); lastBFrameDropTime = curTime; } } } if(queuedPackets.Num()) ReleaseSemaphore(hSendSempahore, 1, NULL); }
/** * Internal check to see if any alarms are ready to be triggered. * * Updates the processor internal interrupt timer to trigger for the next ready alarm. */ void checkAlarms(uint32_t core, OSContext *context) { auto queue = gAlarmQueue[core]; auto now = OSGetTime(); auto next = std::chrono::time_point<std::chrono::system_clock>::max(); coreinit::internal::lockScheduler(); OSUninterruptibleSpinLock_Acquire(gAlarmLock); // Trigger all pending alarms for (OSAlarm *alarm = queue->head; alarm; ) { auto nextAlarm = alarm->link.next; if (alarm->nextFire <= now && alarm->state != OSAlarmState::Cancelled) { triggerAlarmNoLock(queue, alarm, context); } alarm = nextAlarm; } // Find next set alarm for (OSAlarm *alarm = queue->head; alarm; ) { auto nextAlarm = alarm->link.next; if (alarm->state == OSAlarmState::Set && alarm->nextFire) { auto nextFire = coreinit::internal::toTimepoint(alarm->nextFire); if (nextFire < next) { next = nextFire; } } alarm = nextAlarm; } OSUninterruptibleSpinLock_Release(gAlarmLock); coreinit::internal::unlockScheduler(); gProcessor.setInterruptTimer(core, next); }
static bool OSTriggerAlarmNoLock(OSAlarm *alarm, OSContext *context) { alarm->context = context; if (alarm->callback && alarm->state != OSAlarmState::Cancelled) { alarm->callback(alarm, context); } OSWakeupThread(&alarm->threadQueue); if (alarm->period) { alarm->nextFire = OSGetTime() + alarm->period; alarm->state = OSAlarmState::Set; } else { alarm->nextFire = 0; alarm->state = OSAlarmState::None; OSEraseFromQueue(static_cast<OSAlarmQueue*>(alarm->alarmQueue), alarm); } return alarm->nextFire == 0; }
//============================================================================= // BardUtil //============================================================================= BardReal BardUtil_time() { #if defined(_WIN32) struct __timeb64 time_struct; BardReal time_seconds; _ftime64_s( &time_struct ); time_seconds = (BardReal) time_struct.time; time_seconds += time_struct.millitm / 1000.0; return time_seconds; #elif defined(RVL) BardReal time_seconds = OSTicksToMilliseconds(OSGetTime()) / 1000.0; return time_seconds; #else struct timeval time_struct; BardReal time_seconds; gettimeofday( &time_struct, 0 ); time_seconds = (BardReal) time_struct.tv_sec; time_seconds += (time_struct.tv_usec / 1000000.0); return time_seconds; #endif }
/** * Internal alarm handler. * * Resets the alarm state. * Calls the users callback. * Wakes up any threads waiting on the alarm. */ static void triggerAlarmNoLock(OSAlarmQueue *queue, OSAlarm *alarm, OSContext *context) { alarm->context = context; if (alarm->period) { alarm->nextFire = OSGetTime() + alarm->period; alarm->state = OSAlarmState::Set; } else { alarm->nextFire = 0; alarm->state = OSAlarmState::None; alarm->alarmQueue = nullptr; OSEraseFromQueue<OSAlarmQueue>(queue, alarm); } if (alarm->callback) { OSUninterruptibleSpinLock_Release(gAlarmLock); alarm->callback(alarm, context); OSUninterruptibleSpinLock_Acquire(gAlarmLock); } coreinit::internal::wakeupThreadNoLock(&alarm->threadQueue); }
int hello_thread() { int last_tm_sec = -1; uint32_t ip = 0; WHBLogPrintf("Hello World from a std::thread!"); if (!nn::ac::GetAssignedAddress(&ip)) { WHBLogPrintf("GetAssignedAddress failed!"); } WHBLogPrintf("My IP is: %u.%u.%u.%u", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF); while(WHBProcIsRunning()) { OSCalendarTime tm; OSTicksToCalendarTime(OSGetTime(), &tm); if (tm.tm_sec != last_tm_sec) { WHBLogPrintf("%02d/%02d/%04d %02d:%02d:%02d I'm still here.", tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_hour, tm.tm_min, tm.tm_sec); last_tm_sec = tm.tm_sec; } WHBLogConsoleDraw(); OSSleepTicks(OSMillisecondsToTicks(100)); } WHBLogPrintf("Exiting... good bye."); WHBLogConsoleDraw(); OSSleepTicks(OSMillisecondsToTicks(1000)); return 0; }
// Ticks since epoch OSTick OSGetTick() { return OSGetTime() & 0xFFFFFFFF; }
// Time since system start up OSTime OSGetSystemTime() { return OSGetTime() - OSGetSystemInfo()->baseTime; }
//todo: this function is an abomination, this is just disgusting. fix it. //...seriously, this is really, really horrible. I mean this is amazingly bad. void OBS::MainCaptureLoop() { int curRenderTarget = 0, curYUVTexture = 0, curCopyTexture = 0; int copyWait = NUM_RENDER_BUFFERS-1; bSentHeaders = false; bFirstAudioPacket = true; bool bLogLongFramesProfile = GlobalConfig->GetInt(TEXT("General"), TEXT("LogLongFramesProfile"), LOGLONGFRAMESDEFAULT) != 0; float logLongFramesProfilePercentage = GlobalConfig->GetFloat(TEXT("General"), TEXT("LogLongFramesProfilePercentage"), 10.f); Vect2 baseSize = Vect2(float(baseCX), float(baseCY)); Vect2 outputSize = Vect2(float(outputCX), float(outputCY)); Vect2 scaleSize = Vect2(float(scaleCX), float(scaleCY)); HANDLE hScaleVal = yuvScalePixelShader->GetParameterByName(TEXT("baseDimensionI")); //---------------------------------------- // x264 input buffers int curOutBuffer = 0; bool bUsingQSV = videoEncoder->isQSV();//GlobalConfig->GetInt(TEXT("Video Encoding"), TEXT("UseQSV")) != 0; if(bUsingQSV) bUsing444 = false; EncoderPicture lastPic; EncoderPicture outPics[NUM_OUT_BUFFERS]; DWORD outTimes[NUM_OUT_BUFFERS] = {0, 0, 0}; for(int i=0; i<NUM_OUT_BUFFERS; i++) { if(bUsingQSV) { outPics[i].mfxOut = new mfxFrameSurface1; memset(outPics[i].mfxOut, 0, sizeof(mfxFrameSurface1)); mfxFrameData& data = outPics[i].mfxOut->Data; videoEncoder->RequestBuffers(&data); } else { outPics[i].picOut = new x264_picture_t; x264_picture_init(outPics[i].picOut); } } if(bUsing444) { for(int i=0; i<NUM_OUT_BUFFERS; i++) { outPics[i].picOut->img.i_csp = X264_CSP_BGRA; //although the x264 input says BGR, x264 actually will expect packed UYV outPics[i].picOut->img.i_plane = 1; } } else { if(!bUsingQSV) for(int i=0; i<NUM_OUT_BUFFERS; i++) x264_picture_alloc(outPics[i].picOut, X264_CSP_NV12, outputCX, outputCY); } int bCongestionControl = AppConfig->GetInt (TEXT("Video Encoding"), TEXT("CongestionControl"), 0); bool bDynamicBitrateSupported = App->GetVideoEncoder()->DynamicBitrateSupported(); int defaultBitRate = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("MaxBitrate"), 1000); int currentBitRate = defaultBitRate; QWORD lastAdjustmentTime = 0; UINT adjustmentStreamId = 0; //---------------------------------------- // time/timestamp stuff bufferedTimes.Clear(); ctsOffsets.Clear(); int bufferedFrames = 1; //to avoid constantly polling number of frames #ifdef USE_100NS_TIME QWORD streamTimeStart = GetQPCTime100NS(); QWORD frameTime100ns = 10000000/fps; QWORD sleepTargetTime = 0; bool bWasLaggedFrame = false; #else DWORD streamTimeStart = OSGetTime(); DWORD fpsTimeAdjust = 0; #endif totalStreamTime = 0; lastAudioTimestamp = 0; latestVideoTime = firstSceneTimestamp = GetQPCTimeMS(); DWORD fpsTimeNumerator = 1000-(frameTime*fps); DWORD fpsTimeDenominator = fps; DWORD cfrTime = 0; DWORD cfrTimeAdjust = 0; //---------------------------------------- // start audio capture streams desktopAudio->StartCapture(); if(micAudio) micAudio->StartCapture(); //---------------------------------------- // status bar/statistics stuff DWORD fpsCounter = 0; int numLongFrames = 0; int numTotalFrames = 0; int numTotalDuplicatedFrames = 0; bytesPerSec = 0; captureFPS = 0; curFramesDropped = 0; curStrain = 0.0; PostMessage(hwndMain, OBS_UPDATESTATUSBAR, 0, 0); QWORD lastBytesSent[3] = {0, 0, 0}; DWORD lastFramesDropped = 0; #ifdef USE_100NS_TIME double bpsTime = 0.0; #else float bpsTime = 0.0f; #endif double lastStrain = 0.0f; DWORD numSecondsWaited = 0; //---------------------------------------- // 444->420 thread data int numThreads = MAX(OSGetTotalCores()-2, 1); HANDLE *h420Threads = (HANDLE*)Allocate(sizeof(HANDLE)*numThreads); Convert444Data *convertInfo = (Convert444Data*)Allocate(sizeof(Convert444Data)*numThreads); zero(h420Threads, sizeof(HANDLE)*numThreads); zero(convertInfo, sizeof(Convert444Data)*numThreads); for(int i=0; i<numThreads; i++) { convertInfo[i].width = outputCX; convertInfo[i].height = outputCY; convertInfo[i].hSignalConvert = CreateEvent(NULL, FALSE, FALSE, NULL); convertInfo[i].hSignalComplete = CreateEvent(NULL, FALSE, FALSE, NULL); convertInfo[i].bNV12 = bUsingQSV; if(i == 0) convertInfo[i].startY = 0; else convertInfo[i].startY = convertInfo[i-1].endY; if(i == (numThreads-1)) convertInfo[i].endY = outputCY; else convertInfo[i].endY = ((outputCY/numThreads)*(i+1)) & 0xFFFFFFFE; } bool bFirstFrame = true; bool bFirstImage = true; bool bFirst420Encode = true; bool bUseThreaded420 = bUseMultithreadedOptimizations && (OSGetTotalCores() > 1) && !bUsing444; List<HANDLE> completeEvents; if(bUseThreaded420) { for(int i=0; i<numThreads; i++) { h420Threads[i] = OSCreateThread((XTHREAD)Convert444Thread, convertInfo+i); completeEvents << convertInfo[i].hSignalComplete; } } //---------------------------------------- QWORD curStreamTime = 0, lastStreamTime, firstFrameTime = GetQPCTimeMS(); #ifdef USE_100NS_TIME lastStreamTime = GetQPCTime100NS()-frameTime100ns; #else lastStreamTime = firstFrameTime-frameTime; #endif //bool bFirstAudioPacket = true; List<ProfilerNode> threadedProfilers; bool bUsingThreadedProfilers = false; while(bRunning || bufferedFrames) { #ifdef USE_100NS_TIME QWORD renderStartTime = GetQPCTime100NS(); totalStreamTime = DWORD((renderStartTime-streamTimeStart)/10000); if(sleepTargetTime == 0 || bWasLaggedFrame) sleepTargetTime = renderStartTime; #else DWORD renderStartTime = OSGetTime(); totalStreamTime = renderStartTime-streamTimeStart; DWORD frameTimeAdjust = frameTime; fpsTimeAdjust += fpsTimeNumerator; if(fpsTimeAdjust > fpsTimeDenominator) { fpsTimeAdjust -= fpsTimeDenominator; ++frameTimeAdjust; } #endif bool bRenderView = !IsIconic(hwndMain) && bRenderViewEnabled; profileIn("frame"); #ifdef USE_100NS_TIME QWORD qwTime = renderStartTime/10000; latestVideoTime = qwTime; QWORD frameDelta = renderStartTime-lastStreamTime; double fSeconds = double(frameDelta)*0.0000001; //Log(TEXT("frameDelta: %f"), fSeconds); lastStreamTime = renderStartTime; #else QWORD qwTime = GetQPCTimeMS(); latestVideoTime = qwTime; QWORD frameDelta = qwTime-lastStreamTime; float fSeconds = float(frameDelta)*0.001f; //Log(TEXT("frameDelta: %llu"), frameDelta); lastStreamTime = qwTime; #endif bool bUpdateBPS = false; profileIn("frame preprocessing and rendering"); //------------------------------------ if(bRequestKeyframe && keyframeWait > 0) { keyframeWait -= int(frameDelta); if(keyframeWait <= 0) { GetVideoEncoder()->RequestKeyframe(); bRequestKeyframe = false; } } if(!bPushToTalkDown && pushToTalkTimeLeft > 0) { pushToTalkTimeLeft -= int(frameDelta); OSDebugOut(TEXT("time left: %d\r\n"), pushToTalkTimeLeft); if(pushToTalkTimeLeft <= 0) { pushToTalkTimeLeft = 0; bPushToTalkOn = false; } } //------------------------------------ OSEnterMutex(hSceneMutex); if(bResizeRenderView) { GS->ResizeView(); bResizeRenderView = false; } //------------------------------------ if(scene) { profileIn("scene->Preprocess"); scene->Preprocess(); for(UINT i=0; i<globalSources.Num(); i++) globalSources[i].source->Preprocess(); profileOut; scene->Tick(float(fSeconds)); for(UINT i=0; i<globalSources.Num(); i++) globalSources[i].source->Tick(float(fSeconds)); } //------------------------------------ QWORD curBytesSent = network->GetCurrentSentBytes(); curFramesDropped = network->NumDroppedFrames(); bpsTime += fSeconds; if(bpsTime > 1.0f) { if(numSecondsWaited < 3) ++numSecondsWaited; //bytesPerSec = DWORD(curBytesSent - lastBytesSent); bytesPerSec = DWORD(curBytesSent - lastBytesSent[0]) / numSecondsWaited; if(bpsTime > 2.0) bpsTime = 0.0f; else bpsTime -= 1.0; if(numSecondsWaited == 3) { lastBytesSent[0] = lastBytesSent[1]; lastBytesSent[1] = lastBytesSent[2]; lastBytesSent[2] = curBytesSent; } else lastBytesSent[numSecondsWaited] = curBytesSent; captureFPS = fpsCounter; fpsCounter = 0; bUpdateBPS = true; } fpsCounter++; curStrain = network->GetPacketStrain(); EnableBlending(TRUE); BlendFunction(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA); //------------------------------------ // render the mini render texture LoadVertexShader(mainVertexShader); LoadPixelShader(mainPixelShader); SetRenderTarget(mainRenderTextures[curRenderTarget]); Ortho(0.0f, baseSize.x, baseSize.y, 0.0f, -100.0f, 100.0f); SetViewport(0, 0, baseSize.x, baseSize.y); if(scene) scene->Render(); //------------------------------------ if(bTransitioning) { if(!transitionTexture) { transitionTexture = CreateTexture(baseCX, baseCY, GS_BGRA, NULL, FALSE, TRUE); if(transitionTexture) { D3D10Texture *d3dTransitionTex = static_cast<D3D10Texture*>(transitionTexture); D3D10Texture *d3dSceneTex = static_cast<D3D10Texture*>(mainRenderTextures[lastRenderTarget]); GetD3D()->CopyResource(d3dTransitionTex->texture, d3dSceneTex->texture); } else bTransitioning = false; } else if(transitionAlpha >= 1.0f) { delete transitionTexture; transitionTexture = NULL; bTransitioning = false; } } if(bTransitioning) { EnableBlending(TRUE); transitionAlpha += float(fSeconds)*5.0f; if(transitionAlpha > 1.0f) transitionAlpha = 1.0f; } else EnableBlending(FALSE); //------------------------------------ // render the mini view thingy if(bRenderView) { // Cache const Vect2 renderFrameSize = GetRenderFrameSize(); const Vect2 renderFrameOffset = GetRenderFrameOffset(); const Vect2 renderFrameCtrlSize = GetRenderFrameControlSize(); SetRenderTarget(NULL); LoadVertexShader(mainVertexShader); LoadPixelShader(mainPixelShader); Ortho(0.0f, renderFrameCtrlSize.x, renderFrameCtrlSize.y, 0.0f, -100.0f, 100.0f); if(renderFrameCtrlSize.x != oldRenderFrameCtrlWidth || renderFrameCtrlSize.y != oldRenderFrameCtrlHeight) { // User is drag resizing the window. We don't recreate the swap chains so our coordinates are wrong SetViewport(0.0f, 0.0f, (float)oldRenderFrameCtrlWidth, (float)oldRenderFrameCtrlHeight); } else SetViewport(0.0f, 0.0f, renderFrameCtrlSize.x, renderFrameCtrlSize.y); // Draw background (Black if fullscreen, window colour otherwise) if(bFullscreenMode) ClearColorBuffer(0x000000); else ClearColorBuffer(GetSysColor(COLOR_BTNFACE)); if(bTransitioning) { BlendFunction(GS_BLEND_ONE, GS_BLEND_ZERO); DrawSprite(transitionTexture, 0xFFFFFFFF, renderFrameOffset.x, renderFrameOffset.y, renderFrameOffset.x + renderFrameSize.x, renderFrameOffset.y + renderFrameSize.y); BlendFunction(GS_BLEND_FACTOR, GS_BLEND_INVFACTOR, transitionAlpha); } DrawSprite(mainRenderTextures[curRenderTarget], 0xFFFFFFFF, renderFrameOffset.x, renderFrameOffset.y, renderFrameOffset.x + renderFrameSize.x, renderFrameOffset.y + renderFrameSize.y); //draw selections if in edit mode if(bEditMode && !bSizeChanging) { if(scene) { LoadVertexShader(solidVertexShader); LoadPixelShader(solidPixelShader); solidPixelShader->SetColor(solidPixelShader->GetParameter(0), 0xFF0000); scene->RenderSelections(solidPixelShader); } } } else if(bForceRenderViewErase) { InvalidateRect(hwndRenderFrame, NULL, TRUE); UpdateWindow(hwndRenderFrame); bForceRenderViewErase = false; } //------------------------------------ // actual stream output LoadVertexShader(mainVertexShader); LoadPixelShader(yuvScalePixelShader); Texture *yuvRenderTexture = yuvRenderTextures[curRenderTarget]; SetRenderTarget(yuvRenderTexture); if(downscale < 2.01) yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/baseSize); else if(downscale < 3.01) yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/(outputSize*3.0f)); Ortho(0.0f, outputSize.x, outputSize.y, 0.0f, -100.0f, 100.0f); SetViewport(0.0f, 0.0f, outputSize.x, outputSize.y); //why am I using scaleSize instead of outputSize for the texture? //because outputSize can be trimmed by up to three pixels due to 128-bit alignment. //using the scale function with outputSize can cause slightly inaccurate scaled images if(bTransitioning) { BlendFunction(GS_BLEND_ONE, GS_BLEND_ZERO); DrawSpriteEx(transitionTexture, 0xFFFFFFFF, 0.0f, 0.0f, scaleSize.x, scaleSize.y, 0.0f, 0.0f, 1.0f, 1.0f); BlendFunction(GS_BLEND_FACTOR, GS_BLEND_INVFACTOR, transitionAlpha); } DrawSpriteEx(mainRenderTextures[curRenderTarget], 0xFFFFFFFF, 0.0f, 0.0f, outputSize.x, outputSize.y, 0.0f, 0.0f, 1.0f, 1.0f); //------------------------------------ if(bRenderView && !copyWait) static_cast<D3D10System*>(GS)->swap->Present(0, 0); OSLeaveMutex(hSceneMutex); profileOut; //------------------------------------ // present/upload profileIn("video encoding and uploading"); bool bEncode = true; if(copyWait) { copyWait--; bEncode = false; } else { //audio sometimes takes a bit to start -- do not start processing frames until audio has started capturing if(!bRecievedFirstAudioFrame) { static bool bWarnedAboutNoAudio = false; if (qwTime-firstFrameTime > 10000 && !bWarnedAboutNoAudio) { bWarnedAboutNoAudio = true; //AddStreamInfo (TEXT ("WARNING: OBS is not receiving audio frames. Please check your audio devices."), StreamInfoPriority_Critical); } bEncode = false; } else if(bFirstFrame) { firstFrameTime = qwTime; bFirstFrame = false; } if(!bEncode) { if(curYUVTexture == (NUM_RENDER_BUFFERS-1)) curYUVTexture = 0; else curYUVTexture++; } } if(bEncode) { curStreamTime = qwTime-firstFrameTime; UINT prevCopyTexture = (curCopyTexture == 0) ? NUM_RENDER_BUFFERS-1 : curCopyTexture-1; ID3D10Texture2D *copyTexture = copyTextures[curCopyTexture]; profileIn("CopyResource"); if(!bFirst420Encode && bUseThreaded420) { WaitForMultipleObjects(completeEvents.Num(), completeEvents.Array(), TRUE, INFINITE); copyTexture->Unmap(0); } D3D10Texture *d3dYUV = static_cast<D3D10Texture*>(yuvRenderTextures[curYUVTexture]); GetD3D()->CopyResource(copyTexture, d3dYUV->texture); profileOut; ID3D10Texture2D *prevTexture = copyTextures[prevCopyTexture]; if(bFirstImage) //ignore the first frame bFirstImage = false; else { HRESULT result; D3D10_MAPPED_TEXTURE2D map; if(SUCCEEDED(result = prevTexture->Map(0, D3D10_MAP_READ, 0, &map))) { int prevOutBuffer = (curOutBuffer == 0) ? NUM_OUT_BUFFERS-1 : curOutBuffer-1; int nextOutBuffer = (curOutBuffer == NUM_OUT_BUFFERS-1) ? 0 : curOutBuffer+1; EncoderPicture &prevPicOut = outPics[prevOutBuffer]; EncoderPicture &picOut = outPics[curOutBuffer]; EncoderPicture &nextPicOut = outPics[nextOutBuffer]; if(!bUsing444) { profileIn("conversion to 4:2:0"); if(bUseThreaded420) { outTimes[nextOutBuffer] = (DWORD)curStreamTime; bool firstRun = threadedProfilers.Num() == 0; if(firstRun) threadedProfilers.SetSize(numThreads); for(int i=0; i<numThreads; i++) { convertInfo[i].input = (LPBYTE)map.pData; convertInfo[i].inPitch = map.RowPitch; if(bUsingQSV) { mfxFrameData& data = nextPicOut.mfxOut->Data; videoEncoder->RequestBuffers(&data); convertInfo[i].outPitch = data.Pitch; convertInfo[i].output[0] = data.Y; convertInfo[i].output[1] = data.UV; } else { convertInfo[i].output[0] = nextPicOut.picOut->img.plane[0]; convertInfo[i].output[1] = nextPicOut.picOut->img.plane[1]; convertInfo[i].output[2] = nextPicOut.picOut->img.plane[2]; } if(!firstRun) threadedProfilers[i].~ProfilerNode(); ::new (&threadedProfilers[i]) ProfilerNode(TEXT("Convert444Threads"), true); threadedProfilers[i].MonitorThread(h420Threads[i]); bUsingThreadedProfilers = true; SetEvent(convertInfo[i].hSignalConvert); } if(bFirst420Encode) bFirst420Encode = bEncode = false; } else { outTimes[curOutBuffer] = (DWORD)curStreamTime; if(bUsingQSV) { mfxFrameData& data = picOut.mfxOut->Data; videoEncoder->RequestBuffers(&data); LPBYTE output[] = {data.Y, data.UV}; Convert444toNV12((LPBYTE)map.pData, outputCX, map.RowPitch, data.Pitch, outputCY, 0, outputCY, output); } else Convert444toNV12((LPBYTE)map.pData, outputCX, map.RowPitch, outputCX, outputCY, 0, outputCY, picOut.picOut->img.plane); prevTexture->Unmap(0); } profileOut; } else { outTimes[curOutBuffer] = (DWORD)curStreamTime; picOut.picOut->img.i_stride[0] = map.RowPitch; picOut.picOut->img.plane[0] = (uint8_t*)map.pData; } if(bEncode) { DWORD curFrameTimestamp = outTimes[prevOutBuffer]; //Log(TEXT("curFrameTimestamp: %u"), curFrameTimestamp); //------------------------------------ FrameProcessInfo frameInfo; frameInfo.firstFrameTime = firstFrameTime; frameInfo.prevTexture = prevTexture; if(bDupeFrames) { while(cfrTime < curFrameTimestamp) { DWORD frameTimeAdjust = frameTime; cfrTimeAdjust += fpsTimeNumerator; if(cfrTimeAdjust > fpsTimeDenominator) { cfrTimeAdjust -= fpsTimeDenominator; ++frameTimeAdjust; } DWORD halfTime = (frameTimeAdjust+1)/2; EncoderPicture &nextPic = (curFrameTimestamp-cfrTime <= halfTime) ? picOut : prevPicOut; //Log(TEXT("cfrTime: %u, time: %u"), cfrTime, curFrameTimestamp); //these lines are just for counting duped frames if(nextPic == lastPic) ++numTotalDuplicatedFrames; else lastPic = nextPic; frameInfo.pic = &nextPic; if(bUsingQSV) frameInfo.pic->mfxOut->Data.TimeStamp = cfrTime; else frameInfo.pic->picOut->i_pts = cfrTime; frameInfo.frameTimestamp = cfrTime; ProcessFrame(frameInfo); cfrTime += frameTimeAdjust; //Log(TEXT("cfrTime: %u, chi frame: %u"), cfrTime, (curFrameTimestamp-cfrTime <= halfTime)); } } else { if(bUsingQSV) picOut.mfxOut->Data.TimeStamp = curFrameTimestamp; else picOut.picOut->i_pts = curFrameTimestamp; frameInfo.pic = &picOut; frameInfo.frameTimestamp = curFrameTimestamp; ProcessFrame(frameInfo); } if (!bRunning) bufferedFrames = videoEncoder->GetBufferedFrames (); } if(bUsing444) { prevTexture->Unmap(0); } curOutBuffer = nextOutBuffer; } else { //We have to crash, or we end up deadlocking the thread when the convert threads are never signalled if (result == DXGI_ERROR_DEVICE_REMOVED) { String message; HRESULT reason = GetD3D()->GetDeviceRemovedReason(); switch (reason) { case DXGI_ERROR_DEVICE_RESET: case DXGI_ERROR_DEVICE_HUNG: message = TEXT("Your video card or driver froze and was reset. Please check for possible hardware / driver issues."); break; case DXGI_ERROR_DEVICE_REMOVED: message = TEXT("Your video card disappeared from the system. Please check for possible hardware / driver issues."); break; case DXGI_ERROR_DRIVER_INTERNAL_ERROR: message = TEXT("Your video driver reported an internal error. Please check for possible hardware / driver issues."); break; case DXGI_ERROR_INVALID_CALL: message = TEXT("Your video driver reported an invalid call. Please check for possible driver issues."); break; default: message = TEXT("DXGI_ERROR_DEVICE_REMOVED"); break; } CrashError (TEXT("Texture->Map failed: 0x%08x 0x%08x\r\n\r\n%s"), result, reason, message.Array()); } else CrashError (TEXT("Texture->Map failed: 0x%08x"), result); } } if(curCopyTexture == (NUM_RENDER_BUFFERS-1)) curCopyTexture = 0; else curCopyTexture++; if(curYUVTexture == (NUM_RENDER_BUFFERS-1)) curYUVTexture = 0; else curYUVTexture++; if (bCongestionControl && bDynamicBitrateSupported && !bTestStream) { if (curStrain > 25) { if (qwTime - lastAdjustmentTime > 1500) { if (currentBitRate > 100) { currentBitRate = (int)(currentBitRate * (1.0 - (curStrain / 400))); App->GetVideoEncoder()->SetBitRate(currentBitRate, -1); if (!adjustmentStreamId) adjustmentStreamId = App->AddStreamInfo (FormattedString(TEXT("Congestion detected, dropping bitrate to %d kbps"), currentBitRate).Array(), StreamInfoPriority_Low); else App->SetStreamInfo(adjustmentStreamId, FormattedString(TEXT("Congestion detected, dropping bitrate to %d kbps"), currentBitRate).Array()); bUpdateBPS = true; } lastAdjustmentTime = qwTime; } } else if (currentBitRate < defaultBitRate && curStrain < 5 && lastStrain < 5) { if (qwTime - lastAdjustmentTime > 5000) { if (currentBitRate < defaultBitRate) { currentBitRate += (int)(defaultBitRate * 0.05); if (currentBitRate > defaultBitRate) currentBitRate = defaultBitRate; } App->GetVideoEncoder()->SetBitRate(currentBitRate, -1); /*if (!adjustmentStreamId) App->AddStreamInfo (FormattedString(TEXT("Congestion clearing, raising bitrate to %d kbps"), currentBitRate).Array(), StreamInfoPriority_Low); else App->SetStreamInfo(adjustmentStreamId, FormattedString(TEXT("Congestion clearing, raising bitrate to %d kbps"), currentBitRate).Array());*/ bUpdateBPS = true; lastAdjustmentTime = qwTime; } } } } lastRenderTarget = curRenderTarget; if(curRenderTarget == (NUM_RENDER_BUFFERS-1)) curRenderTarget = 0; else curRenderTarget++; if(bUpdateBPS || !CloseDouble(curStrain, lastStrain) || curFramesDropped != lastFramesDropped) { PostMessage(hwndMain, OBS_UPDATESTATUSBAR, 0, 0); lastStrain = curStrain; lastFramesDropped = curFramesDropped; } //------------------------------------ // we're about to sleep so we should flush the d3d command queue profileIn("flush"); GetD3D()->Flush(); profileOut; profileOut; //video encoding and uploading profileOut; //frame //------------------------------------ // frame sync #ifdef USE_100NS_TIME QWORD renderStopTime = GetQPCTime100NS(); sleepTargetTime += frameTime100ns; if(bWasLaggedFrame = (sleepTargetTime <= renderStopTime)) { numLongFrames++; if(bLogLongFramesProfile && (numLongFrames/float(max(1, numTotalFrames)) * 100.) > logLongFramesProfilePercentage) DumpLastProfileData(); } else SleepTo(sleepTargetTime); #else DWORD renderStopTime = OSGetTime(); DWORD totalTime = renderStopTime-renderStartTime; if(totalTime > frameTimeAdjust) { numLongFrames++; if(bLogLongFramesProfile && (numLongFrames/float(max(1, numTotalFrames)) * 100.) > logLongFramesProfilePercentage) DumpLastProfileData(); } else if(totalTime < frameTimeAdjust) OSSleep(frameTimeAdjust-totalTime); #endif //OSDebugOut(TEXT("Frame adjust time: %d, "), frameTimeAdjust-totalTime); numTotalFrames++; } if(!bUsing444) { if(bUseThreaded420) { for(int i=0; i<numThreads; i++) { if(h420Threads[i]) { convertInfo[i].bKillThread = true; SetEvent(convertInfo[i].hSignalConvert); if(bUsingThreadedProfilers) threadedProfilers[i].~ProfilerNode(); OSTerminateThread(h420Threads[i], 10000); h420Threads[i] = NULL; } if(convertInfo[i].hSignalConvert) { CloseHandle(convertInfo[i].hSignalConvert); convertInfo[i].hSignalConvert = NULL; } if(convertInfo[i].hSignalComplete) { CloseHandle(convertInfo[i].hSignalComplete); convertInfo[i].hSignalComplete = NULL; } } if(!bFirst420Encode) { ID3D10Texture2D *copyTexture = copyTextures[curCopyTexture]; copyTexture->Unmap(0); } } if(bUsingQSV) for(int i = 0; i < NUM_OUT_BUFFERS; i++) delete outPics[i].mfxOut; else for(int i=0; i<NUM_OUT_BUFFERS; i++) { x264_picture_clean(outPics[i].picOut); delete outPics[i].picOut; } } Free(h420Threads); Free(convertInfo); Log(TEXT("Total frames rendered: %d, number of frames that lagged: %d (%0.2f%%) (it's okay for some frames to lag)"), numTotalFrames, numLongFrames, (double(numLongFrames)/double(numTotalFrames))*100.0); if(bDupeFrames) Log(TEXT("Total duplicated frames: %d (%0.2f%%)"), numTotalDuplicatedFrames, (double(numTotalDuplicatedFrames)/double(numTotalFrames))*100.0); }
DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher) { //------------------------------------------------------ // set up URL bool bRetry = false; bool bSuccess = false; bool bCanRetry = false; String failReason; String strBindIP; int serviceID = AppConfig->GetInt (TEXT("Publish"), TEXT("Service")); String strURL = AppConfig->GetString(TEXT("Publish"), TEXT("URL")); String strPlayPath = AppConfig->GetString(TEXT("Publish"), TEXT("PlayPath")); strURL.KillSpaces(); strPlayPath.KillSpaces(); LPSTR lpAnsiURL = NULL, lpAnsiPlaypath = NULL; RTMP *rtmp = NULL; //-------------------------------- // unbelievably disgusting hack for elgato devices String strOldDirectory; UINT dirSize = GetCurrentDirectory(0, 0); strOldDirectory.SetLength(dirSize); GetCurrentDirectory(dirSize, strOldDirectory.Array()); OSSetCurrentDirectory(API->GetAppPath()); //-------------------------------- if(!strURL.IsValid()) { failReason = TEXT("No server specified to connect to"); goto end; } if(serviceID != 0) { XConfig serverData; if(!serverData.Open(TEXT("services.xconfig"))) { failReason = TEXT("Could not open services.xconfig"); goto end; } XElement *services = serverData.GetElement(TEXT("services")); if(!services) { failReason = TEXT("Could not find any services in services.xconfig"); goto end; } XElement *service = NULL; DWORD numServices = services->NumElements(); for(UINT i=0; i<numServices; i++) { XElement *curService = services->GetElementByID(i); if(curService->GetInt(TEXT("id")) == serviceID) { service = curService; break; } } if(!service) { failReason = TEXT("Could not find the service specified in services.xconfig"); goto end; } XElement *servers = service->GetElement(TEXT("servers")); if(!servers) { failReason = TEXT("Could not find any servers for the service specified in services.xconfig"); goto end; } XDataItem *item = servers->GetDataItem(strURL); if(!item) item = servers->GetDataItemByID(0); strURL = item->GetData(); Log(TEXT("Using RTMP service: %s"), service->GetName()); Log(TEXT(" Server selection: %s"), strURL.Array()); } //------------------------------------------------------ // now back to the elgato directory if it needs the directory changed still to function *sigh* OSSetCurrentDirectory(strOldDirectory); //------------------------------------------------------ rtmp = RTMP_Alloc(); RTMP_Init(rtmp); RTMP_LogSetCallback(librtmpErrorCallback); //RTMP_LogSetLevel(RTMP_LOGERROR); lpAnsiURL = strURL.CreateUTF8String(); lpAnsiPlaypath = strPlayPath.CreateUTF8String(); if(!RTMP_SetupURL2(rtmp, lpAnsiURL, lpAnsiPlaypath)) { failReason = Str("Connection.CouldNotParseURL"); goto end; } char *rtmpUser = AppConfig->GetString(TEXT("Publish"), TEXT("Username")).CreateUTF8String(); char *rtmpPass = AppConfig->GetString(TEXT("Publish"), TEXT("Password")).CreateUTF8String(); if (rtmpUser) { rtmp->Link.pubUser.av_val = rtmpUser; rtmp->Link.pubUser.av_len = (int)strlen(rtmpUser); } if (rtmpPass) { rtmp->Link.pubPasswd.av_val = rtmpPass; rtmp->Link.pubPasswd.av_len = (int)strlen(rtmpPass); } RTMP_EnableWrite(rtmp); //set it to publish rtmp->Link.swfUrl.av_len = rtmp->Link.tcUrl.av_len; rtmp->Link.swfUrl.av_val = rtmp->Link.tcUrl.av_val; /*rtmp->Link.pageUrl.av_len = rtmp->Link.tcUrl.av_len; rtmp->Link.pageUrl.av_val = rtmp->Link.tcUrl.av_val;*/ rtmp->Link.flashVer.av_val = "FMLE/3.0 (compatible; FMSc/1.0)"; rtmp->Link.flashVer.av_len = (int)strlen(rtmp->Link.flashVer.av_val); //----------------------------------------- UINT tcpBufferSize = AppConfig->GetInt(TEXT("Publish"), TEXT("TCPBufferSize"), 64*1024); if(tcpBufferSize < 8192) tcpBufferSize = 8192; else if(tcpBufferSize > 1024*1024) tcpBufferSize = 1024*1024; rtmp->m_outChunkSize = 4096;//RTMP_DEFAULT_CHUNKSIZE;// rtmp->m_bSendChunkSizeInfo = TRUE; rtmp->m_bUseNagle = TRUE; strBindIP = AppConfig->GetString(TEXT("Publish"), TEXT("BindToIP"), TEXT("Default")); if (scmp(strBindIP, TEXT("Default"))) { rtmp->m_bindIP.addr.sin_family = AF_INET; rtmp->m_bindIP.addrLen = sizeof(rtmp->m_bindIP.addr); if (WSAStringToAddress(strBindIP.Array(), AF_INET, NULL, (LPSOCKADDR)&rtmp->m_bindIP.addr, &rtmp->m_bindIP.addrLen) == SOCKET_ERROR) { // no localization since this should rarely/never happen failReason = TEXT("WSAStringToAddress: Could not parse address"); goto end; } } LogInterfaceType(rtmp); //----------------------------------------- if(!RTMP_Connect(rtmp, NULL)) { failReason = Str("Connection.CouldNotConnect"); failReason << TEXT("\r\n\r\n") << RTMPPublisher::GetRTMPErrors(); bCanRetry = true; goto end; } if(!RTMP_ConnectStream(rtmp, 0)) { failReason = Str("Connection.InvalidStream"); failReason << TEXT("\r\n\r\n") << RTMPPublisher::GetRTMPErrors(); bCanRetry = true; goto end; } //----------------------------------------- OSDebugOut(TEXT("Connected: %u\r\n"), OSGetTime()); publisher->RequestKeyframe(1000); //----------------------------------------- bSuccess = true; end: if (lpAnsiURL) Free(lpAnsiURL); if (lpAnsiPlaypath) Free(lpAnsiPlaypath); if(!bSuccess) { if(rtmp) { RTMP_Close(rtmp); RTMP_Free(rtmp); } if(failReason.IsValid()) App->SetStreamReport(failReason); if(!publisher->bStopping) PostMessage(hwndMain, OBS_REQUESTSTOP, bCanRetry ? 0 : 1, 0); Log(TEXT("Connection to %s failed: %s"), strURL.Array(), failReason.Array()); publisher->bStopping = true; } else { publisher->Init(rtmp, tcpBufferSize); publisher->bConnected = true; publisher->bConnecting = false; } return 0; }
void RTMPPublisher::SendPacketForReal(BYTE *data, UINT size, DWORD timestamp, PacketType type) { //OSDebugOut (TEXT("%u: SendPacketForReal (%d bytes - %08x @ %u, type %d)\n"), OSGetTime(), size, quickHash(data,size), timestamp, type); //Log(TEXT("packet| timestamp: %u, type: %u, bytes: %u"), timestamp, (UINT)type, size); OSEnterMutex(hDataMutex); if(bConnected) { ProcessPackets(); bool bSend = bSentFirstKeyframe; if(!bSentFirstKeyframe) { if(type == PacketType_VideoHighest) { bSend = true; OSDebugOut(TEXT("got keyframe: %u\r\n"), OSGetTime()); } } if(bSend) { if(!bSentFirstAudio && type == PacketType_Audio) { timestamp = 0; bSentFirstAudio = true; } totalFrames++; if(type != PacketType_Audio) totalVideoFrames++; bool bAddPacket = false; if(type >= packetWaitType) { if(type != PacketType_Audio) packetWaitType = PacketType_VideoDisposable; bAddPacket = true; } if(bAddPacket) { List<BYTE> paddedData; paddedData.SetSize(size+RTMP_MAX_HEADER_SIZE); mcpy(paddedData.Array()+RTMP_MAX_HEADER_SIZE, data, size); if(!bSentFirstKeyframe) { DataPacket sei; App->GetVideoEncoder()->GetSEI(sei); paddedData.InsertArray(RTMP_MAX_HEADER_SIZE+5, sei.lpPacket, sei.size); bSentFirstKeyframe = true; } currentBufferSize += paddedData.Num(); UINT droppedFrameVal = queuedPackets.Num() ? queuedPackets.Last().distanceFromDroppedFrame+1 : 10000; UINT id = FindClosestQueueIndex(timestamp); NetworkPacket *queuedPacket = queuedPackets.InsertNew(id); queuedPacket->distanceFromDroppedFrame = droppedFrameVal; queuedPacket->data.TransferFrom(paddedData); queuedPacket->timestamp = timestamp; queuedPacket->type = type; } else { if(type < PacketType_VideoHigh) numBFramesDumped++; else numPFramesDumped++; } } } OSLeaveMutex(hDataMutex); }
/** * Run N tasks from queue. * * Does not remove tasks from queue. * Can be run from multiple threads at once. * * Side Effects: * - Sets state to Stopped if state is Stopping and tasksRunning reaches 0. * - Sets state to Finished if all tasks are finished. * - TasksReady -> TasksRunning -> TasksFinished. * * Returns TRUE if at least 1 task is run. */ BOOL MPRunTasksFromTaskQ(MPTaskQueue *queue, uint32_t tasks) { BOOL result = FALSE; while (queue->state == MPTaskQueueState::Ready) { uint32_t first, count, available; OSUninterruptibleSpinLock_Acquire(&queue->lock); available = queue->queueSize - queue->queueIndex; count = std::min(available, tasks); first = queue->queueIndex; tasks -= count; queue->tasksReady -= count; queue->tasksRunning += count; queue->queueIndex += count; OSUninterruptibleSpinLock_Release(&queue->lock); if (count == 0) { // Nothing to run, lets go home! break; } // Result is TRUE if at least 1 task is run result = TRUE; // Mark all tasks as running for (auto i = 0u; i < count; ++i) { auto task = queue->queue[first + i]; task->state = MPTaskState::Running; task->coreID = OSGetCoreId(); } // Run all tasks for (auto i = 0u; i < count; ++i) { auto task = queue->queue[first + i]; auto start = OSGetTime(); task->result = task->func(task->userArg1, task->userArg2); task->state = MPTaskState::Finished; task->duration = OSGetTime() - start; } OSUninterruptibleSpinLock_Acquire(&queue->lock); queue->tasksRunning -= count; queue->tasksFinished += count; if (queue->state == MPTaskQueueState::Stopping && queue->tasksRunning == 0) { queue->state = MPTaskQueueState::Stopped; } if (queue->tasks == queue->tasksFinished) { queue->state = MPTaskQueueState::Finished; } OSUninterruptibleSpinLock_Release(&queue->lock); } return result; }
void RTMPPublisher::SocketLoop() { bool canWrite = false; int delayTime; int latencyPacketSize; DWORD lastSendTime = 0; WSANETWORKEVENTS networkEvents; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); WSAEventSelect(rtmp->m_sb.sb_socket, hWriteEvent, FD_READ|FD_WRITE|FD_CLOSE); //Low latency mode works by delaying delayTime ms between calls to send() and only sending //a buffer as large as latencyPacketSize at once. This causes keyframes and other data bursts //to be sent over several sends instead of one large one. if (lowLatencyMode == LL_MODE_AUTO) { //Auto mode aims for a constant rate of whatever the stream bitrate is and segments into //MTU sized packets (test packet captures indicated that despite nagling being enabled, //the size of the send() buffer is still important for some reason). Note that delays //become very short at this rate, and it can take a while for the buffer to empty after //a keyframe. delayTime = 1400.0f / (dataBufferSize / 1000.0f); latencyPacketSize = 1460; } else if (lowLatencyMode == LL_MODE_FIXED) { //We use latencyFactor - 2 to guarantee we're always sending at a slightly higher //rate than the maximum expected data rate so we don't get backed up latencyPacketSize = dataBufferSize / (latencyFactor - 2); delayTime = 1000 / latencyFactor; } else { latencyPacketSize = dataBufferSize; delayTime = 0; } SetupSendBacklogEvent (); HANDLE hObjects[3]; hObjects[0] = hWriteEvent; hObjects[1] = hBufferEvent; hObjects[2] = hSendBacklogEvent; for (;;) { if (bStopping && WaitForSingleObject(hSocketLoopExit, 0) != WAIT_TIMEOUT) { OSEnterMutex(hDataBufferMutex); if (curDataBufferLen == 0) { //OSDebugOut (TEXT("Exiting on empty buffer.\n")); OSLeaveMutex(hDataBufferMutex); break; } //OSDebugOut (TEXT("Want to exit, but %d bytes remain.\n"), curDataBufferLen); OSLeaveMutex(hDataBufferMutex); } int status = WaitForMultipleObjects (3, hObjects, FALSE, INFINITE); if (status == WAIT_ABANDONED || status == WAIT_FAILED) { Log(TEXT("RTMPPublisher::SocketLoop: Aborting due to WaitForMultipleObjects failure")); App->PostStopMessage(); return; } if (status == WAIT_OBJECT_0) { //Socket event if (WSAEnumNetworkEvents (rtmp->m_sb.sb_socket, NULL, &networkEvents)) { Log(TEXT("RTMPPublisher::SocketLoop: Aborting due to WSAEnumNetworkEvents failure, %d"), WSAGetLastError()); App->PostStopMessage(); return; } if (networkEvents.lNetworkEvents & FD_WRITE) canWrite = true; if (networkEvents.lNetworkEvents & FD_CLOSE) { if (bStopping) Log(TEXT("RTMPPublisher::SocketLoop: Aborting due to FD_CLOSE during shutdown, %d bytes lost, error %d"), curDataBufferLen, networkEvents.iErrorCode[FD_CLOSE_BIT]); else Log(TEXT("RTMPPublisher::SocketLoop: Aborting due to FD_CLOSE, error %d"), networkEvents.iErrorCode[FD_CLOSE_BIT]); FatalSocketShutdown (); return; } if (networkEvents.lNetworkEvents & FD_READ) { BYTE discard[16384]; int ret, errorCode; BOOL fatalError = FALSE; for (;;) { ret = recv(rtmp->m_sb.sb_socket, (char *)discard, sizeof(discard), 0); if (ret == -1) { errorCode = WSAGetLastError(); if (errorCode == WSAEWOULDBLOCK) break; fatalError = TRUE; } else if (ret == 0) { errorCode = 0; fatalError = TRUE; } if (fatalError) { Log(TEXT("RTMPPublisher::SocketLoop: Socket error, recv() returned %d, GetLastError() %d"), ret, errorCode); FatalSocketShutdown (); return; } } } } else if (status == WAIT_OBJECT_0 + 2) { //Ideal send backlog event ULONG idealSendBacklog; if (!idealsendbacklogquery(rtmp->m_sb.sb_socket, &idealSendBacklog)) { int curTCPBufSize, curTCPBufSizeSize = sizeof(curTCPBufSize); getsockopt (rtmp->m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, (char *)&curTCPBufSize, &curTCPBufSizeSize); if (curTCPBufSize < (int)idealSendBacklog) { int bufferSize = (int)idealSendBacklog; setsockopt(rtmp->m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, (const char *)&bufferSize, sizeof(bufferSize)); Log(TEXT("RTMPPublisher::Socketloop: Increasing send buffer to ISB %d (buffer: %d / %d)"), idealSendBacklog, curDataBufferLen, dataBufferSize); } } SetupSendBacklogEvent (); continue; } if (canWrite) { bool exitLoop = false; do { OSEnterMutex(hDataBufferMutex); if (!curDataBufferLen) { //this is now an expected occasional condition due to use of auto-reset events, we could end up emptying the buffer //as it's filled in a previous loop cycle, especially if using low latency mode. OSLeaveMutex(hDataBufferMutex); //Log(TEXT("RTMPPublisher::SocketLoop: Trying to send, but no data available?!")); break; } int ret; if (lowLatencyMode != LL_MODE_NONE) { int sendLength = min (latencyPacketSize, curDataBufferLen); ret = send(rtmp->m_sb.sb_socket, (const char *)dataBuffer, sendLength, 0); } else { ret = send(rtmp->m_sb.sb_socket, (const char *)dataBuffer, curDataBufferLen, 0); } if (ret > 0) { if (curDataBufferLen - ret) memmove(dataBuffer, dataBuffer + ret, curDataBufferLen - ret); curDataBufferLen -= ret; bytesSent += ret; if (lastSendTime) { totalSendPeriod += OSGetTime() - lastSendTime; totalSendBytes += ret; totalSendCount++; } lastSendTime = OSGetTime(); SetEvent(hBufferSpaceAvailableEvent); } else { int errorCode; BOOL fatalError = FALSE; if (ret == -1) { errorCode = WSAGetLastError(); if (errorCode == WSAEWOULDBLOCK) { canWrite = false; OSLeaveMutex(hDataBufferMutex); break; } fatalError = TRUE; } else if (ret == 0) { errorCode = 0; fatalError = TRUE; } if (fatalError) { //connection closed, or connection was aborted / socket closed / etc, that's a fatal error for us. Log(TEXT("RTMPPublisher::SocketLoop: Socket error, send() returned %d, GetLastError() %d"), ret, errorCode); OSLeaveMutex(hDataBufferMutex); FatalSocketShutdown (); return; } } //finish writing for now if (curDataBufferLen <= 1000) exitLoop = true; OSLeaveMutex(hDataBufferMutex); if (delayTime) Sleep (delayTime); } while (!exitLoop); } } Log(TEXT("RTMPPublisher::SocketLoop: Graceful loop exit")); }
bool OBS::SetScene(CTSTR lpScene) { if(bDisableSceneSwitching) return false; HWND hwndScenes = GetDlgItem(hwndMain, ID_SCENES); UINT curSel = (UINT)SendMessage(hwndScenes, LB_GETCURSEL, 0, 0); //------------------------- if(curSel != LB_ERR) { UINT textLen = (UINT)SendMessage(hwndScenes, LB_GETTEXTLEN, curSel, 0); String strLBName; strLBName.SetLength(textLen); SendMessage(hwndScenes, LB_GETTEXT, curSel, (LPARAM)strLBName.Array()); if(!strLBName.CompareI(lpScene)) { UINT id = (UINT)SendMessage(hwndScenes, LB_FINDSTRINGEXACT, -1, (LPARAM)lpScene); if(id == LB_ERR) return false; SendMessage(hwndScenes, LB_SETCURSEL, id, 0); } } else { UINT id = (UINT)SendMessage(hwndScenes, LB_FINDSTRINGEXACT, -1, (LPARAM)lpScene); if(id == LB_ERR) return false; SendMessage(hwndScenes, LB_SETCURSEL, id, 0); } //------------------------- XElement *scenes = scenesConfig.GetElement(TEXT("scenes")); XElement *newSceneElement = scenes->GetElement(lpScene); if(!newSceneElement) return false; if(sceneElement == newSceneElement) return true; sceneElement = newSceneElement; CTSTR lpClass = sceneElement->GetString(TEXT("class")); if(!lpClass) { AppWarning(TEXT("OBS::SetScene: no class found for scene '%s'"), newSceneElement->GetName()); return false; } DWORD sceneChangeStartTime; if(bRunning) { Log(TEXT("++++++++++++++++++++++++++++++++++++++++++++++++++++++")); Log(TEXT(" New Scene")); sceneChangeStartTime = OSGetTime(); } XElement *sceneData = newSceneElement->GetElement(TEXT("data")); //------------------------- Scene *newScene = NULL; if(bRunning) newScene = CreateScene(lpClass, sceneData); //------------------------- HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); SendMessage(hwndSources, WM_SETREDRAW, (WPARAM)FALSE, (LPARAM) 0); App->scaleItem = NULL; bChangingSources = true; ListView_DeleteAllItems(hwndSources); bool bSkipTransition = false; XElement *sources = sceneElement->GetElement(TEXT("sources")); if(sources) { UINT numSources = sources->NumElements(); ListView_SetItemCount(hwndSources, numSources); for(UINT i=0; i<numSources; i++) { XElement *sourceElement = sources->GetElementByID(i); String className = sourceElement->GetString(TEXT("class")); if(className == "DeviceCapture") { // There's a capture device in the next scene that isn't a global source, // so let's skip the transition since it won't work anyway. bSkipTransition = true; } } for(UINT i=0; i<numSources; i++) { XElement *sourceElement = sources->GetElementByID(i); bool render = sourceElement->GetInt(TEXT("render"), 1) > 0; InsertSourceItem(i, (LPWSTR)sourceElement->GetName(), render); // Do not add image sources yet in case we're skipping the transition. // This fixes the issue where capture devices sources that used the // same device as one in the previous scene would just go blank // after switching. if(bRunning && newScene && !bSkipTransition) newScene->AddImageSource(sourceElement); } } bChangingSources = false; SendMessage(hwndSources, WM_SETREDRAW, (WPARAM)TRUE, (LPARAM) 0); RedrawWindow(hwndSources, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); if(scene && newScene && newScene->HasMissingSources()) MessageBox(hwndMain, Str("Scene.MissingSources"), NULL, 0); if(bRunning) { //todo: cache scenes maybe? undecided. not really as necessary with global sources OSEnterMutex(hSceneMutex); UINT numSources; if (scene) { //shutdown previous scene, if any numSources = scene->sceneItems.Num(); for(UINT i=0; i<numSources; i++) { XElement *source = scene->sceneItems[i]->GetElement(); String className = source->GetString(TEXT("class")); if(scene->sceneItems[i]->bRender && className == "GlobalSource") { XElement *globalSourceData = source->GetElement(TEXT("data")); String globalSourceName = globalSourceData->GetString(TEXT("name")); if(App->GetGlobalSource(globalSourceName) != NULL) { App->GetGlobalSource(globalSourceName)->GlobalSourceLeaveScene(); } } } scene->EndScene(); } Scene *previousScene = scene; scene = newScene; if(newScene && bSkipTransition) { // If we're skipping the transition because of a non-global // DirectShow device, delete the scene here and add the // ImageSources at this point instead. delete previousScene; if(sources) { UINT numSources = sources->NumElements(); for(UINT i=0; i<numSources; i++) { XElement *sourceElement = sources->GetElementByID(i); if(newScene) newScene->AddImageSource(sourceElement); } } } scene->BeginScene(); numSources = scene->sceneItems.Num(); for(UINT i=0; i<numSources; i++) { XElement *source = scene->sceneItems[i]->GetElement(); String className = source->GetString(TEXT("class")); if(scene->sceneItems[i]->bRender && className == "GlobalSource") { XElement *globalSourceData = source->GetElement(TEXT("data")); String globalSourceName = globalSourceData->GetString(TEXT("name")); if(App->GetGlobalSource(globalSourceName) != NULL) { App->GetGlobalSource(globalSourceName)->GlobalSourceEnterScene(); } } } if(!bTransitioning && !bSkipTransition) { bTransitioning = true; transitionAlpha = 0.0f; } OSLeaveMutex(hSceneMutex); if(!bSkipTransition) { // Do not delete the previous scene here, since it has already // been deleted. delete previousScene; } DWORD sceneChangeTime = OSGetTime() - sceneChangeStartTime; if (sceneChangeTime >= 500) Log(TEXT("PERFORMANCE WARNING: Scene change took %u ms, maybe some sources should be global sources?"), sceneChangeTime); } if(API != NULL) ReportSwitchScenes(lpScene); return true; }