1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// A template for a MediaSource encapsulating an audio/video input device
19//
20// NOTE: Sections of this code labeled "%%% TO BE WRITTEN %%%" are incomplete, and need to be written by the programmer
21// (depending on the features of the particular device).
22// Implementation
23
24#include "DeviceSource.hh"
25#include <GroupsockHelper.hh> // for "gettimeofday()"
26
27DeviceSource*
28DeviceSource::createNew(UsageEnvironment& env,
29 DeviceParameters params) {
30 return new DeviceSource(env, params);
31}
32
33EventTriggerId DeviceSource::eventTriggerId = 0;
34
35unsigned DeviceSource::referenceCount = 0;
36
37DeviceSource::DeviceSource(UsageEnvironment& env,
38 DeviceParameters params)
39 : FramedSource(env), fParams(params) {
40 if (referenceCount == 0) {
41 // Any global initialization of the device would be done here:
42 //%%% TO BE WRITTEN %%%
43 }
44 ++referenceCount;
45
46 // Any instance-specific initialization of the device would be done here:
47 //%%% TO BE WRITTEN %%%
48
49 // We arrange here for our "deliverFrame" member function to be called
50 // whenever the next frame of data becomes available from the device.
51 //
52 // If the device can be accessed as a readable socket, then one easy way to do this is using a call to
53 // envir().taskScheduler().turnOnBackgroundReadHandling( ... )
54 // (See examples of this call in the "liveMedia" directory.)
55 //
56 // If, however, the device *cannot* be accessed as a readable socket, then instead we can implement it using 'event triggers':
57 // Create an 'event trigger' for this device (if it hasn't already been done):
58 if (eventTriggerId == 0) {
59 eventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0);
60 }
61}
62
63DeviceSource::~DeviceSource() {
64 // Any instance-specific 'destruction' (i.e., resetting) of the device would be done here:
65 //%%% TO BE WRITTEN %%%
66
67 --referenceCount;
68 if (referenceCount == 0) {
69 // Any global 'destruction' (i.e., resetting) of the device would be done here:
70 //%%% TO BE WRITTEN %%%
71
72 // Reclaim our 'event trigger'
73 envir().taskScheduler().deleteEventTrigger(eventTriggerId);
74 eventTriggerId = 0;
75 }
76}
77
78void DeviceSource::doGetNextFrame() {
79 // This function is called (by our 'downstream' object) when it asks for new data.
80
81 // Note: If, for some reason, the source device stops being readable (e.g., it gets closed), then you do the following:
82 if (0 /* the source stops being readable */ /*%%% TO BE WRITTEN %%%*/) {
83 handleClosure();
84 return;
85 }
86
87 // If a new frame of data is immediately available to be delivered, then do this now:
88 if (0 /* a new frame of data is immediately available to be delivered*/ /*%%% TO BE WRITTEN %%%*/) {
89 deliverFrame();
90 }
91
92 // No new data is immediately available to be delivered. We don't do anything more here.
93 // Instead, our event trigger must be called (e.g., from a separate thread) when new data becomes available.
94}
95
96void DeviceSource::deliverFrame0(void* clientData) {
97 ((DeviceSource*)clientData)->deliverFrame();
98}
99
100void DeviceSource::deliverFrame() {
101 // This function is called when new frame data is available from the device.
102 // We deliver this data by copying it to the 'downstream' object, using the following parameters (class members):
103 // 'in' parameters (these should *not* be modified by this function):
104 // fTo: The frame data is copied to this address.
105 // (Note that the variable "fTo" is *not* modified. Instead,
106 // the frame data is copied to the address pointed to by "fTo".)
107 // fMaxSize: This is the maximum number of bytes that can be copied
108 // (If the actual frame is larger than this, then it should
109 // be truncated, and "fNumTruncatedBytes" set accordingly.)
110 // 'out' parameters (these are modified by this function):
111 // fFrameSize: Should be set to the delivered frame size (<= fMaxSize).
112 // fNumTruncatedBytes: Should be set iff the delivered frame would have been
113 // bigger than "fMaxSize", in which case it's set to the number of bytes
114 // that have been omitted.
115 // fPresentationTime: Should be set to the frame's presentation time
116 // (seconds, microseconds). This time must be aligned with 'wall-clock time' - i.e., the time that you would get
117 // by calling "gettimeofday()".
118 // fDurationInMicroseconds: Should be set to the frame's duration, if known.
119 // If, however, the device is a 'live source' (e.g., encoded from a camera or microphone), then we probably don't need
120 // to set this variable, because - in this case - data will never arrive 'early'.
121 // Note the code below.
122
123 if (!isCurrentlyAwaitingData()) return; // we're not ready for the data yet
124
125 u_int8_t* newFrameDataStart = (u_int8_t*)0xDEADBEEF; //%%% TO BE WRITTEN %%%
126 unsigned newFrameSize = 0; //%%% TO BE WRITTEN %%%
127
128 // Deliver the data here:
129 if (newFrameSize > fMaxSize) {
130 fFrameSize = fMaxSize;
131 fNumTruncatedBytes = newFrameSize - fMaxSize;
132 } else {
133 fFrameSize = newFrameSize;
134 }
135 gettimeofday(&fPresentationTime, NULL); // If you have a more accurate time - e.g., from an encoder - then use that instead.
136 // If the device is *not* a 'live source' (e.g., it comes instead from a file or buffer), then set "fDurationInMicroseconds" here.
137 memmove(fTo, newFrameDataStart, fFrameSize);
138
139 // After delivering the data, inform the reader that it is now available:
140 FramedSource::afterGetting(this);
141}
142
143
144// The following code would be called to signal that a new frame of data has become available.
145// This (unlike other "LIVE555 Streaming Media" library code) may be called from a separate thread.
146// (Note, however, that "triggerEvent()" cannot be called with the same 'event trigger id' from different threads.
147// Also, if you want to have multiple device threads, each one using a different 'event trigger id', then you will need
148// to make "eventTriggerId" a non-static member variable of "DeviceSource".)
149void signalNewFrameData() {
150 TaskScheduler* ourScheduler = NULL; //%%% TO BE WRITTEN %%%
151 DeviceSource* ourDevice = NULL; //%%% TO BE WRITTEN %%%
152
153 if (ourScheduler != NULL) { // sanity check
154 ourScheduler->triggerEvent(DeviceSource::eventTriggerId, ourDevice);
155 }
156}
157