RDK Documentation (Open Sourced RDK Components)
webvttParser.cpp
Go to the documentation of this file.
1 /*
2  * If not stated otherwise in this file or this component's license file the
3  * following copyright and licenses apply:
4  *
5  * Copyright 2018 RDK Management
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18 */
19 
20 /**
21  * @file webvttParser.cpp
22  *
23  * @brief Parser impl for WebVTT subtitle fragments
24  *
25  */
26 
27 #include <string.h>
28 #include <assert.h>
29 #include <cctype>
30 #include <algorithm>
31 #include "webvttParser.h"
32 
33 //Macros
34 #define CHAR_CARRIAGE_RETURN '\r'
35 #define CHAR_LINE_FEED '\n'
36 #define CHAR_SPACE ' '
37 
38 #define VTT_QUEUE_TIMER_INTERVAL 250 //milliseconds
39 
40 /// Variable initialization for supported cue line alignment values
41 std::vector<std::string> allowedCueLineAligns = { "start", "center", "end" };
42 
43 /// Variable initialization for supported cue position alignment values
44 std::vector<std::string> allowedCuePosAligns = { "line-left", "center", "line-right" };
45 
46 /// Variable initialization for supported cue text alignment values
47 std::vector<std::string> allowedCueTextAligns = { "start", "center", "end", "left", "right" };
48 
49 
50 /***************************************************************************
51 * @fn parsePercentageValueSetting
52 * @brief Function to parse the value in percentage format
53 *
54 * @param settingValue[in] value to be parsed
55 * @return char* pointer to the parsed value
56 ***************************************************************************/
57 static char * parsePercentageValueSetting(char *settingValue)
58 {
59  char *ret = NULL;
60  char *percentageSym = strchr(settingValue, '%');
61  if ((std::isdigit( static_cast<unsigned char>(settingValue[0]) ) != 0) && (percentageSym != NULL))
62  {
63  *percentageSym = '\0';
64  ret = settingValue;
65  }
66  return ret;
67 }
68 
69 
70 /***************************************************************************
71 * @fn findWebVTTLineBreak
72 * @brief Function to extract a line from a VTT fragment
73 *
74 * @param buffer[in] VTT data to extract from
75 * @return char* pointer to extracted line
76 ***************************************************************************/
77 static char * findWebVTTLineBreak(char *buffer)
78 {
79  //VTT has CR and LF as line terminators or both
80  char *lineBreak = strpbrk(buffer, "\r\n");
81  char *next = NULL;
82  if (lineBreak)
83  {
84  next = lineBreak + 1;
85  //For CR, LF pair cases
86  if (*lineBreak == CHAR_CARRIAGE_RETURN && *next == CHAR_LINE_FEED)
87  {
88  next += 1;
89  }
90  *lineBreak = '\0';
91  }
92  return next;
93 }
94 
95 
96 /***************************************************************************
97 * @fn convertHHMMSSToTime
98 * @brief Function to convert time in HH:MM:SS.MS format to milliseconds
99 *
100 * @param str[in] time in HH:MM:SS.MS format
101 * @return long long equivalent time in milliseconds
102 ***************************************************************************/
103 static long long convertHHMMSSToTime(char *str)
104 {
105  long long timeValueMs = 0;
106  //HH:MM:SS.MS
107  char *args[4] = { str, NULL, NULL, NULL };
108  int argCount = 1;
109  while(*(++str) != '\0' && argCount < 4)
110  {
111  if(*str == ':' || *str == '.')
112  {
113  args[argCount++] = (str + 1);
114  *str = '\0';
115  }
116  }
117 
118  if (argCount == 1)
119  {
120  AAMPLOG_ERR("Unsupported value received!");
121  }
122  //HH:MM:SS.MS
123  else
124  {
125  timeValueMs = atoll(args[--argCount]);
126  int multiplier = 1;
127  while (argCount > 0)
128  {
129  timeValueMs += (atoll(args[--argCount]) * multiplier * 1000);
130  if (argCount > 0)
131  {
132  multiplier *= 60;
133  }
134  }
135  }
136  return timeValueMs;
137 }
138 
139 
140 /***************************************************************************
141  * @fn SendVttCueToExt
142  * @brief Timer's callback to send WebVTT cues to external app
143  *
144  * @param[in] user_data pointer to WebVTTParser instance
145  * @retval G_SOURCE_REMOVE, if the source should be removed
146 ***************************************************************************/
147 static gboolean SendVttCueToExt(gpointer user_data)
148 {
149  WebVTTParser *parser = (WebVTTParser *) user_data;
150  parser->sendCueData();
151  return G_SOURCE_CONTINUE;
152 }
153 
154 
155 /***************************************************************************
156 * @fn WebVTTParser
157 * @brief Constructor function
158 *
159 * @param aamp[in] PrivateInstanceAAMP pointer
160 * @param type[in] VTT data type
161 * @return void
162 ***************************************************************************/
163 WebVTTParser::WebVTTParser(AampLogManager* logObj, PrivateInstanceAAMP *aamp, SubtitleMimeType type) : SubtitleParser(logObj, aamp, type),
164  mStartPTS(0), mCurrentPos(0), mStartPos(0), mPtsOffset(0),
165  mReset(true), mVttQueue(), mVttQueueIdleTaskId(0), mVttQueueMutex(), lastCue(),
166  mProgressOffset(0)
167 {
168  pthread_mutex_init(&mVttQueueMutex, NULL);
169  lastCue = { 0, 0 };
170 }
171 
172 
173 /***************************************************************************
174 * @fn ~WebVTTParser
175 * @brief Destructor function
176 *
177 * @return void
178 ***************************************************************************/
179 WebVTTParser::~WebVTTParser()
180 {
181  close();
182  pthread_mutex_destroy(&mVttQueueMutex);
183 }
184 
185 
186 /***************************************************************************
187 * @fn init
188 * @brief Initializes the parser instance
189 *
190 * @param startPos[in] playlist start position in milliseconds
191 * @param basePTS[in] base PTS value
192 * @return bool true if successful, false otherwise
193 ***************************************************************************/
194 bool WebVTTParser::init(double startPosSeconds, unsigned long long basePTS)
195 {
196  bool ret = true;
197  mStartPTS = basePTS;
198 
199  if (mVttQueueIdleTaskId == 0)
200  {
201  mVttQueueIdleTaskId = g_timeout_add(VTT_QUEUE_TIMER_INTERVAL, SendVttCueToExt, this);
202  }
203 
204  AAMPLOG_WARN("WebVTTParser::startPos:%.3f and mStartPTS:%lld", startPosSeconds, mStartPTS);
205  //We are ready to receive data, unblock in PrivateInstanceAAMP
207  return ret;
208 }
209 
210 
211 /***************************************************************************
212 * @fn processData
213 * @brief Parse incoming VTT data
214 *
215 * @param buffer[in] input VTT data
216 * @param bufferLen[in] data length
217 * @param position[in] position of buffer
218 * @param duration[in] duration of buffer
219 * @return bool true if successful, false otherwise
220 ***************************************************************************/
221 bool WebVTTParser::processData(char* buffer, size_t bufferLen, double position, double duration)
222 {
223  bool ret = false;
224 
225  AAMPLOG_TRACE("WebVTTParser::Enter with position:%.3f and duration:%.3f ", position, duration);
226 
227  if (mReset)
228  {
229  mStartPos = (position * 1000) - mProgressOffset;
230  mReset = false;
231  AAMPLOG_WARN("WebVTTParser::Received first buffer after reset with mStartPos:%.3f", mStartPos);
232  }
233 
234  //Check for VTT signature at the start of buffer
235  if (bufferLen > 6)
236  {
237  char *next = findWebVTTLineBreak(buffer);
238  if (next && strlen(buffer) >= 6)
239  {
240  char *token = strtok(buffer, " \t\n\r");
241  if(token != NULL)
242  {
243  //VTT is UTF-8 encoded and BOM is 0xEF,0xBB,0xBF
244  if ((unsigned char) token[0] == 0xEF && (unsigned char) token[1] == 0xBB && (unsigned char) token[2] == 0xBF)
245  {
246  //skip BOM
247  token += 3;
248  }
249  if (strlen(token) == 6 && strncmp(token, "WEBVTT", 6) == 0)
250  {
251  buffer = next;
252  ret = true;
253  }
254  }
255  else
256  {
257  AAMPLOG_WARN("token is null"); //CID:85449,85515 - Null Returns
258  }
259  }
260  }
261 
262  if (ret)
263  {
264  while (buffer)
265  {
266  char *nextLine = findWebVTTLineBreak(buffer);
267  //TODO: Parse CUE ID
268 
269  if (strstr(buffer, "X-TIMESTAMP-MAP") != NULL)
270  {
271  unsigned long long mpegTime = 0;
272  unsigned long long localTime = 0;
273  //Found X-TIMESTAMP-MAP=LOCAL:<cue time>,MPEGTS:<MPEG-2 time>
274  char* token = strtok(buffer, "=,");
275  while (token)
276  {
277  if (token[0] == 'L')
278  {
279  localTime = convertHHMMSSToTime(token + 6);
280  }
281  else if (token[0] == 'M')
282  {
283  mpegTime = atoll(token + 7);
284  }
285  token = strtok(NULL, "=,");
286  }
287  mPtsOffset = (mpegTime / 90) - localTime; //in milliseconds
288  AAMPLOG_INFO("Parsed local time:%lld and PTS:%lld and cuePTSOffset:%lld", localTime, mpegTime, mPtsOffset);
289  }
290  else if (strstr(buffer, " --> ") != NULL)
291  {
292  AAMPLOG_INFO("Found cue:%s", buffer);
293  long long start = -1;
294  long long end = -1;
295  char *text = NULL;
296  //cue settings
297  char *cueLine = NULL; //default "auto"
298  char *cueLineAlign = NULL; //default "start"
299  char *cuePosition = NULL; //default "auto"
300  char *cuePosAlign = NULL; //default "auto"
301  char *cueSize = NULL; //default 100
302  char *cueTextAlign = NULL; //default "center"
303 
304  char *token = strtok(buffer, " -->\t");
305  while (token != NULL)
306  {
307  AAMPLOG_TRACE("Inside tokenizer, token:%s", token);
308  if (std::isdigit( static_cast<unsigned char>(token[0]) ) != 0)
309  {
310  if (start == -1)
311  {
312  start = convertHHMMSSToTime(token);
313  }
314  else if (end == -1)
315  {
316  end = convertHHMMSSToTime(token);
317  }
318  }
319  //TODO:parse cue settings
320  else
321  {
322  //for setting ':' is the delimiter
323  char *key = token;
324  char *value = strchr(token, ':');
325  if (value != NULL)
326  {
327  *value = '\0';
328  value++;
329  if (strncmp(key, "line", 4) == 0)
330  {
331  char *lineAlign = strchr(value, ',');
332  if (lineAlign != NULL)
333  {
334  *lineAlign = '\0';
335  lineAlign++;
336  if (std::find(allowedCueLineAligns.begin(), allowedCueLineAligns.end(), lineAlign) != allowedCueLineAligns.end())
337  {
338  cueLineAlign = lineAlign;
339  }
340  }
341  cueLine = parsePercentageValueSetting(value);
342  }
343  else if (strncmp(key, "position", 8) == 0)
344  {
345  char *posAlign = strchr(value, ',');
346  if (posAlign != NULL)
347  {
348  *posAlign = '\0';
349  posAlign++;
350  if (std::find(allowedCuePosAligns.begin(), allowedCuePosAligns.end(), posAlign) != allowedCuePosAligns.end())
351  {
352  cuePosAlign = posAlign;
353  }
354  }
355  cuePosition = parsePercentageValueSetting(value);
356  }
357  else if (strncmp(key, "size", 4) == 0)
358  {
359  cueSize = parsePercentageValueSetting(value);
360  }
361  else if (strncmp(key, "align", 5) == 0)
362  {
363  if (std::find(allowedCueTextAligns.begin(), allowedCueTextAligns.end(), value) != allowedCueTextAligns.end())
364  {
365  cueTextAlign = value;
366  }
367  }
368  }
369  }
370  token = strtok(NULL, " -->\t");
371  }
372 
373  text = nextLine;
374  nextLine = findWebVTTLineBreak(nextLine);
375  while(nextLine && (*nextLine != CHAR_LINE_FEED && *nextLine != CHAR_CARRIAGE_RETURN && *nextLine != '\0'))
376  {
377  AAMPLOG_TRACE("Found nextLine:%s", nextLine);
378  if (nextLine[-1] == '\0')
379  {
380  nextLine[-1] = '\n';
381  }
382  if (nextLine[-2] == '\0')
383  {
384  nextLine[-2] = '\r';
385  }
386  nextLine = findWebVTTLineBreak(nextLine);
387  }
388  double cueStartInMpegTime = (start + mPtsOffset);
389  double duration = (end - start);
390  double mpegTimeOffset = cueStartInMpegTime - (mStartPTS / 90);
391  double relativeStartPos = mStartPos + mpegTimeOffset; //w.r.t to position in reportProgress
392  AAMPLOG_INFO("So found cue with startPTS:%.3f and duration:%.3f, and mpegTimeOffset:%.3f and relative time being:%.3f", cueStartInMpegTime/1000.0, duration/1000.0, mpegTimeOffset/1000.0, relativeStartPos/1000.0);
393  addCueData(new VTTCue(relativeStartPos, duration, std::string(text), std::string()));
394  }
395 
396  buffer = nextLine;
397  }
398  }
399  mCurrentPos = (position + duration) * 1000.0;
400  AAMPLOG_TRACE("################# Exit sub PTS:%.3f", mCurrentPos);
401  return ret;
402 }
403 
404 
405 /***************************************************************************
406 * @fn close
407 * @brief Close and release all resources
408 *
409  @return bool true if successful, false otherwise
410 ***************************************************************************/
411 bool WebVTTParser::close()
412 {
413  bool ret = true;
414  if (mVttQueueIdleTaskId != 0)
415  {
416  AAMPLOG_INFO("WebVTTParser:: Remove mVttQueueIdleTaskId %d", mVttQueueIdleTaskId);
417  g_source_remove(mVttQueueIdleTaskId);
419  }
420 
421  pthread_mutex_lock(&mVttQueueMutex);
422  if (!mVttQueue.empty())
423  {
424  while(mVttQueue.size() > 0)
425  {
426  VTTCue *cue = mVttQueue.front();
427  mVttQueue.pop();
428  SAFE_DELETE(cue);
429  }
430  }
431  pthread_mutex_unlock(&mVttQueueMutex);
432 
433  lastCue.mStart = 0;
434  lastCue.mDuration = 0;
435  mProgressOffset = 0;
436 
437  return ret;
438 }
439 
440 
441 /***************************************************************************
442 * @fn reset
443 * @brief Reset the parser
444 *
445 * @return void
446 ***************************************************************************/
447 void WebVTTParser::reset()
448 {
449  //Called on discontinuity, blocks further VTT processing
450  //Blocked until we get new basePTS
451  AAMPLOG_WARN("WebVTTParser::Reset subtitle parser at position:%.3f", mCurrentPos);
452  //Avoid calling stop injection if the first buffer is discontinuous
453  if (!mReset)
454  {
456  }
457  mPtsOffset = 0;
458  mStartPTS = 0;
459  mStartPos = 0;
460  mReset = true;
461 }
462 
463 
464 /***************************************************************************
465 * @fn addCueData
466 * @brief Add cue to queue
467 *
468 * @param cue[in] pointer to cue to store
469 * @return void
470 ***************************************************************************/
471 void WebVTTParser::addCueData(VTTCue *cue)
472 {
473  if (lastCue.mStart != cue->mStart || lastCue.mDuration != cue->mDuration)
474  {
475  pthread_mutex_lock(&mVttQueueMutex);
476  mVttQueue.push(cue);
477  pthread_mutex_unlock(&mVttQueueMutex);
478  }
479  lastCue.mStart = cue->mStart;
480  lastCue.mDuration = cue->mDuration;
481 }
482 
483 
484 /***************************************************************************
485 * @fn sendCueData
486 * @brief Send cues stored in queue to AAMP
487 *
488 * @return void
489 ***************************************************************************/
490 void WebVTTParser::sendCueData()
491 {
492  pthread_mutex_lock(&mVttQueueMutex);
493  if (!mVttQueue.empty())
494  {
495  while(mVttQueue.size() > 0)
496  {
497  VTTCue *cue = mVttQueue.front();
498  mVttQueue.pop();
499  if (cue->mStart > 0)
500  {
501  mAamp->SendVTTCueDataAsEvent(cue);
502  }
503  else
504  {
505  AAMPLOG_WARN("Discarding cue with start:%.3f and text:%s", cue->mStart/1000.0, cue->mText.c_str());
506  }
507  SAFE_DELETE(cue);
508  }
509  }
510  pthread_mutex_unlock(&mVttQueueMutex);
511 }
512 
513 /**
514  * @}
515  */
WebVTTParser::mProgressOffset
double mProgressOffset
Definition: webvttParser.h:85
PrivateInstanceAAMP::SendVTTCueDataAsEvent
void SendVTTCueDataAsEvent(VTTCue *cue)
To send webvtt cue as an event.
Definition: priv_aamp.cpp:9188
webvttParser.h
WebVTT parser implementation for AAMP.
AampLogManager
AampLogManager Class.
Definition: AampLogManager.h:150
WebVTTParser::mVttQueueMutex
pthread_mutex_t mVttQueueMutex
Definition: webvttParser.h:84
allowedCueTextAligns
std::vector< std::string > allowedCueTextAligns
Variable initialization for supported cue text alignment values.
Definition: webvttParser.cpp:47
WebVTTParser::mReset
bool mReset
Definition: webvttParser.h:78
WebVTTParser::mPtsOffset
unsigned long long mPtsOffset
Definition: webvttParser.h:75
PrivateInstanceAAMP::StopTrackDownloads
void StopTrackDownloads(MediaType type)
Stop downloads for a track. Called from StreamSink to control flow.
Definition: priv_aamp.cpp:3179
allowedCueLineAligns
std::vector< std::string > allowedCueLineAligns
Variable initialization for supported cue line alignment values.
Definition: webvttParser.cpp:41
allowedCuePosAligns
std::vector< std::string > allowedCuePosAligns
Variable initialization for supported cue position alignment values.
Definition: webvttParser.cpp:44
WebVTTParser::mStartPTS
unsigned long long mStartPTS
Definition: webvttParser.h:74
SubtitleMimeType
SubtitleMimeType
Subtitle data types.
Definition: subtitleParser.h:37
VTTCue
Data structure to hold a VTT cue.
Definition: vttCue.h:39
WebVTTParser::mCurrentPos
double mCurrentPos
Definition: webvttParser.h:77
WebVTTParser::lastCue
CueTimeStamp lastCue
Definition: webvttParser.h:80
WebVTTParser::mStartPos
double mStartPos
Definition: webvttParser.h:76
AAMPLOG_TRACE
#define AAMPLOG_TRACE(FORMAT,...)
AAMP logging defines, this can be enabled through setLogLevel() as per the need.
Definition: AampLogManager.h:83
PrivateInstanceAAMP
Class representing the AAMP player's private instance, which is not exposed to outside world.
Definition: priv_aamp.h:640
WebVTTParser::mVttQueueIdleTaskId
guint mVttQueueIdleTaskId
Definition: webvttParser.h:83
WebVTTParser::mVttQueue
std::queue< VTTCue * > mVttQueue
Definition: webvttParser.h:82
eMEDIATYPE_SUBTITLE
@ eMEDIATYPE_SUBTITLE
Definition: AampMediaType.h:41
WebVTTParser
WebVTT parser class.
Definition: webvttParser.h:56
PrivateInstanceAAMP::ResumeTrackDownloads
void ResumeTrackDownloads(MediaType type)
Resume downloads for a track. Called from StreamSink to control flow.
Definition: priv_aamp.cpp:3202
SubtitleParser
Subtitle parser class.
Definition: subtitleParser.h:52