/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved. // A template for a MediaSource encapsulating an audio/video input device // // NOTE: Sections of this code labeled "%%% TO BE WRITTEN %%%" are incomplete, and need to be written by the programmer // (depending on the features of the particular device). // Implementation #include "DeviceSource.hh" #include // for "gettimeofday()" DeviceSource* DeviceSource::createNew(UsageEnvironment& env, DeviceParameters params) { return new DeviceSource(env, params); } EventTriggerId DeviceSource::eventTriggerId = 0; unsigned DeviceSource::referenceCount = 0; DeviceSource::DeviceSource(UsageEnvironment& env, DeviceParameters params) : FramedSource(env), fParams(params) { if (referenceCount == 0) { // Any global initialization of the device would be done here: //%%% TO BE WRITTEN %%% } ++referenceCount; // Any instance-specific initialization of the device would be done here: //%%% TO BE WRITTEN %%% // We arrange here for our "deliverFrame" member function to be called // whenever the next frame of data becomes available from the device. // // If the device can be accessed as a readable socket, then one easy way to do this is using a call to // envir().taskScheduler().turnOnBackgroundReadHandling( ... ) // (See examples of this call in the "liveMedia" directory.) // // If, however, the device *cannot* be accessed as a readable socket, then instead we can implement it using 'event triggers': // Create an 'event trigger' for this device (if it hasn't already been done): if (eventTriggerId == 0) { eventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0); } } DeviceSource::~DeviceSource() { // Any instance-specific 'destruction' (i.e., resetting) of the device would be done here: //%%% TO BE WRITTEN %%% --referenceCount; if (referenceCount == 0) { // Any global 'destruction' (i.e., resetting) of the device would be done here: //%%% TO BE WRITTEN %%% // Reclaim our 'event trigger' envir().taskScheduler().deleteEventTrigger(eventTriggerId); eventTriggerId = 0; } } void DeviceSource::doGetNextFrame() { // This function is called (by our 'downstream' object) when it asks for new data. // Note: If, for some reason, the source device stops being readable (e.g., it gets closed), then you do the following: if (0 /* the source stops being readable */ /*%%% TO BE WRITTEN %%%*/) { handleClosure(); return; } // If a new frame of data is immediately available to be delivered, then do this now: if (0 /* a new frame of data is immediately available to be delivered*/ /*%%% TO BE WRITTEN %%%*/) { deliverFrame(); } // No new data is immediately available to be delivered. We don't do anything more here. // Instead, our event trigger must be called (e.g., from a separate thread) when new data becomes available. } void DeviceSource::deliverFrame0(void* clientData) { ((DeviceSource*)clientData)->deliverFrame(); } void DeviceSource::deliverFrame() { // This function is called when new frame data is available from the device. // We deliver this data by copying it to the 'downstream' object, using the following parameters (class members): // 'in' parameters (these should *not* be modified by this function): // fTo: The frame data is copied to this address. // (Note that the variable "fTo" is *not* modified. Instead, // the frame data is copied to the address pointed to by "fTo".) // fMaxSize: This is the maximum number of bytes that can be copied // (If the actual frame is larger than this, then it should // be truncated, and "fNumTruncatedBytes" set accordingly.) // 'out' parameters (these are modified by this function): // fFrameSize: Should be set to the delivered frame size (<= fMaxSize). // fNumTruncatedBytes: Should be set iff the delivered frame would have been // bigger than "fMaxSize", in which case it's set to the number of bytes // that have been omitted. // fPresentationTime: Should be set to the frame's presentation time // (seconds, microseconds). This time must be aligned with 'wall-clock time' - i.e., the time that you would get // by calling "gettimeofday()". // fDurationInMicroseconds: Should be set to the frame's duration, if known. // If, however, the device is a 'live source' (e.g., encoded from a camera or microphone), then we probably don't need // to set this variable, because - in this case - data will never arrive 'early'. // Note the code below. if (!isCurrentlyAwaitingData()) return; // we're not ready for the data yet u_int8_t* newFrameDataStart = (u_int8_t*)0xDEADBEEF; //%%% TO BE WRITTEN %%% unsigned newFrameSize = 0; //%%% TO BE WRITTEN %%% // Deliver the data here: if (newFrameSize > fMaxSize) { fFrameSize = fMaxSize; fNumTruncatedBytes = newFrameSize - fMaxSize; } else { fFrameSize = newFrameSize; } gettimeofday(&fPresentationTime, NULL); // If you have a more accurate time - e.g., from an encoder - then use that instead. // If the device is *not* a 'live source' (e.g., it comes instead from a file or buffer), then set "fDurationInMicroseconds" here. memmove(fTo, newFrameDataStart, fFrameSize); // After delivering the data, inform the reader that it is now available: FramedSource::afterGetting(this); } // The following code would be called to signal that a new frame of data has become available. // This (unlike other "LIVE555 Streaming Media" library code) may be called from a separate thread. // (Note, however, that "triggerEvent()" cannot be called with the same 'event trigger id' from different threads. // Also, if you want to have multiple device threads, each one using a different 'event trigger id', then you will need // to make "eventTriggerId" a non-static member variable of "DeviceSource".) void signalNewFrameData() { TaskScheduler* ourScheduler = NULL; //%%% TO BE WRITTEN %%% DeviceSource* ourDevice = NULL; //%%% TO BE WRITTEN %%% if (ourScheduler != NULL) { // sanity check ourScheduler->triggerEvent(DeviceSource::eventTriggerId, ourDevice); } }