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// File sinks
19// Implementation
20
21#if (defined(__WIN32__) || defined(_WIN32)) && !defined(_WIN32_WCE)
22#include <io.h>
23#include <fcntl.h>
24#endif
25#include "FileSink.hh"
26#include "GroupsockHelper.hh"
27#include "OutputFile.hh"
28
29////////// FileSink //////////
30
31FileSink::FileSink(UsageEnvironment& env, FILE* fid, unsigned bufferSize,
32 char const* perFrameFileNamePrefix)
33 : MediaSink(env), fOutFid(fid), fBufferSize(bufferSize), fSamePresentationTimeCounter(0) {
34 fBuffer = new unsigned char[bufferSize];
35 if (perFrameFileNamePrefix != NULL) {
36 fPerFrameFileNamePrefix = strDup(perFrameFileNamePrefix);
37 fPerFrameFileNameBuffer = new char[strlen(perFrameFileNamePrefix) + 100];
38 } else {
39 fPerFrameFileNamePrefix = NULL;
40 fPerFrameFileNameBuffer = NULL;
41 }
42 fPrevPresentationTime.tv_sec = ~0; fPrevPresentationTime.tv_usec = 0;
43}
44
45FileSink::~FileSink() {
46 delete[] fPerFrameFileNameBuffer;
47 delete[] fPerFrameFileNamePrefix;
48 delete[] fBuffer;
49 if (fOutFid != NULL) fclose(fOutFid);
50}
51
52FileSink* FileSink::createNew(UsageEnvironment& env, char const* fileName,
53 unsigned bufferSize, Boolean oneFilePerFrame) {
54 do {
55 FILE* fid;
56 char const* perFrameFileNamePrefix;
57 if (oneFilePerFrame) {
58 // Create the fid for each frame
59 fid = NULL;
60 perFrameFileNamePrefix = fileName;
61 } else {
62 // Normal case: create the fid once
63 fid = OpenOutputFile(env, fileName);
64 if (fid == NULL) break;
65 perFrameFileNamePrefix = NULL;
66 }
67
68 return new FileSink(env, fid, bufferSize, perFrameFileNamePrefix);
69 } while (0);
70
71 return NULL;
72}
73
74Boolean FileSink::continuePlaying() {
75 if (fSource == NULL) return False;
76
77 fSource->getNextFrame(fBuffer, fBufferSize,
78 afterGettingFrame, this,
79 onSourceClosure, this);
80
81 return True;
82}
83
84void FileSink::afterGettingFrame(void* clientData, unsigned frameSize,
85 unsigned numTruncatedBytes,
86 struct timeval presentationTime,
87 unsigned /*durationInMicroseconds*/) {
88 FileSink* sink = (FileSink*)clientData;
89 sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);
90}
91
92void FileSink::addData(unsigned char const* data, unsigned dataSize,
93 struct timeval presentationTime) {
94 if (fPerFrameFileNameBuffer != NULL && fOutFid == NULL) {
95 // Special case: Open a new file on-the-fly for this frame
96 if (presentationTime.tv_usec == fPrevPresentationTime.tv_usec &&
97 presentationTime.tv_sec == fPrevPresentationTime.tv_sec) {
98 // The presentation time is unchanged from the previous frame, so we add a 'counter'
99 // suffix to the file name, to distinguish them:
100 sprintf(fPerFrameFileNameBuffer, "%s-%lu.%06lu-%u", fPerFrameFileNamePrefix,
101 presentationTime.tv_sec, presentationTime.tv_usec, ++fSamePresentationTimeCounter);
102 } else {
103 sprintf(fPerFrameFileNameBuffer, "%s-%lu.%06lu", fPerFrameFileNamePrefix,
104 presentationTime.tv_sec, presentationTime.tv_usec);
105 fPrevPresentationTime = presentationTime; // for next time
106 fSamePresentationTimeCounter = 0; // for next time
107 }
108 fOutFid = OpenOutputFile(envir(), fPerFrameFileNameBuffer);
109 }
110
111 // Write to our file:
112#ifdef TEST_LOSS
113 static unsigned const framesPerPacket = 10;
114 static unsigned const frameCount = 0;
115 static Boolean const packetIsLost;
116 if ((frameCount++)%framesPerPacket == 0) {
117 packetIsLost = (our_random()%10 == 0); // simulate 10% packet loss #####
118 }
119
120 if (!packetIsLost)
121#endif
122 if (fOutFid != NULL && data != NULL) {
123 fwrite(data, 1, dataSize, fOutFid);
124 }
125}
126
127void FileSink::afterGettingFrame(unsigned frameSize,
128 unsigned numTruncatedBytes,
129 struct timeval presentationTime) {
130 if (numTruncatedBytes > 0) {
131 envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size ("
132 << fBufferSize << "). "
133 << numTruncatedBytes << " bytes of trailing data was dropped! Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call to at least "
134 << fBufferSize + numTruncatedBytes << "\n";
135 }
136 addData(fBuffer, frameSize, presentationTime);
137
138 if (fOutFid == NULL || fflush(fOutFid) == EOF) {
139 // The output file has closed. Handle this the same way as if the input source had closed:
140 if (fSource != NULL) fSource->stopGettingFrames();
141 onSourceClosure();
142 return;
143 }
144
145 if (fPerFrameFileNameBuffer != NULL) {
146 if (fOutFid != NULL) { fclose(fOutFid); fOutFid = NULL; }
147 }
148
149 // Then try getting the next frame:
150 continuePlaying();
151}
152