Torc  0.1
torcmuxer.cpp
Go to the documentation of this file.
1 /* Class TorcMuxer
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2018
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Qt
24 #include <QMutex>
25 #include <QString>
26 
27 // Torc
28 #include "torclogging.h"
29 #include "torcmuxer.h"
30 
31 // FFmpeg
32 extern "C" {
33 #include <libavcodec/avcodec.h>
34 }
35 
36 #define FFMPEG_BUFFER_SIZE 64*1024
37 
38 static void TorcAVLog(void *Ptr, int Level, const char *Fmt, va_list VAL)
39 {
41  return;
42 
43  static QString line(QStringLiteral(""));
44  static const int length = 255;
45  static QMutex lock;
46  uint64_t verboseMask = VB_GENERAL;
47  int verboseLevel = LOG_DEBUG;
48 
49  switch (Level)
50  {
51  case AV_LOG_PANIC:
52  verboseLevel = LOG_EMERG;
53  break;
54  case AV_LOG_FATAL:
55  verboseLevel = LOG_CRIT;
56  break;
57  case AV_LOG_ERROR:
58  verboseLevel = LOG_ERR;
59  break;
60  case AV_LOG_DEBUG:
61  case AV_LOG_VERBOSE:
62  verboseLevel = LOG_DEBUG;
63  break;
64  case AV_LOG_INFO:
65  verboseLevel = LOG_INFO;
66  break;
67  case AV_LOG_WARNING:
68  verboseLevel = LOG_WARNING;
69  break;
70  default:
71  return;
72  }
73 
74  if (!VERBOSE_LEVEL_CHECK(verboseMask, verboseLevel))
75  return;
76 
77  lock.lock();
78  if (line.isEmpty() && Ptr) {
79  AVClass* avc = *(AVClass**)Ptr;
80  line.sprintf("[%s @ %p] ", avc->item_name(Ptr), avc);
81  }
82 
83  char str[length+1];
84  int bytes = vsnprintf(str, length+1, Fmt, VAL);
85 
86  // check for truncated messages and fix them
87  if (bytes > length)
88  {
89  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Libav log output truncated %1 of %2 bytes written").arg(length).arg(bytes));
90  str[length - 1] = '\n';
91  }
92 
93  line += QString(str);
94  if (line.endsWith(QStringLiteral("\n")))
95  {
96  LOG(verboseMask, verboseLevel, line.trimmed());
97  line.truncate(0);
98  }
99  lock.unlock();
100 }
101 
103  : m_formatCtx(nullptr),
104  m_created(false),
105  m_started(false),
106  m_outputFile(),
107  m_ringBuffer(Buffer),
108  m_ioContext(nullptr),
109  m_audioContext(nullptr),
110  m_audioStream(0),
111  m_audioFrame(nullptr),
112  m_audioPacket(nullptr),
113  m_lastVideoPts(AV_NOPTS_VALUE),
114  m_lastAudioPts(AV_NOPTS_VALUE)
115 {
116  av_log_set_callback(TorcAVLog);
117  SetupContext();
118  SetupIO();
119 }
120 
121 TorcMuxer::TorcMuxer(const QString &File)
122  : m_formatCtx(nullptr),
123  m_created(false),
124  m_started(false),
125  m_outputFile(File),
126  m_ringBuffer(nullptr),
127  m_ioContext(nullptr),
128  m_audioContext(nullptr),
129  m_audioStream(0),
130  m_audioFrame(nullptr),
131  m_audioPacket(nullptr),
132  m_lastVideoPts(AV_NOPTS_VALUE),
133  m_lastAudioPts(AV_NOPTS_VALUE)
134 {
135  SetupContext();
136  SetupIO();
137 }
138 
140 {
141  // TODO check whether this is safe with file output
142  if (m_ioContext)
143  {
144  if (m_ioContext->buffer)
145  {
146  av_free(m_ioContext->buffer);
147  m_ioContext->buffer = nullptr;
148  }
149  av_free(m_ioContext);
150  m_ioContext = nullptr;
151  }
152 
153  if (m_formatCtx)
154  {
155  avformat_free_context(m_formatCtx);
156  m_formatCtx = nullptr;
157  }
158 
159  if (m_audioFrame)
160  {
161  av_frame_free(&m_audioFrame);
162  m_audioFrame = nullptr;
163  }
164 
165  if (m_audioPacket)
166  {
167  av_packet_free(&m_audioPacket);
168  m_audioPacket = nullptr;
169  }
170 
171  if (m_audioContext)
172  {
173  avcodec_free_context(&m_audioContext);
174  m_audioContext = nullptr;
175  }
176 }
177 
178 void TorcMuxer::SetupContext(void)
179 {
180 #if (LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58,9,100))
181  av_register_all();
182 #endif
183 
184  AVOutputFormat *format = av_guess_format("mp4", nullptr, nullptr);
185  if (!format)
186  {
187  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to find MPEGTS muxer"));
188  return;
189  }
190 
191  // create context
192  m_formatCtx = avformat_alloc_context();
193  if (!m_formatCtx)
194  {
195  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to create AVFormatContext"));
196  return;
197  }
198  m_formatCtx->oformat = format;
199 }
200 
201 void TorcMuxer::SetupIO(void)
202 {
203  if (!m_formatCtx)
204  return;
205 
206  if (!m_outputFile.isEmpty())
207  {
208  // create output file
209  if (avio_open(&m_formatCtx->pb, m_outputFile.toLocal8Bit().constData(), AVIO_FLAG_WRITE) < 0)
210  {
211  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open '%1' for output").arg(m_outputFile));
212  return;
213  }
214  m_created = true;
215  return;
216  }
217 
218  if (m_ringBuffer)
219  {
220  uint8_t* iobuffer = (uint8_t *)av_malloc(FFMPEG_BUFFER_SIZE);
221  m_ioContext = avio_alloc_context(iobuffer, FFMPEG_BUFFER_SIZE, 1, this, nullptr, &AVWritePacket, nullptr);
222  m_formatCtx->pb = m_ioContext;
223  m_formatCtx->flags = AVFMT_FLAG_CUSTOM_IO;
224  m_created = true;
225  return;
226  }
227 }
228 
229 int TorcMuxer::AVWritePacket(void *Opaque, uint8_t *Buffer, int Size)
230 {
231  TorcMuxer* muxer = static_cast<TorcMuxer*>(Opaque);
232  if (!muxer)
233  return -1;
234 
235  return muxer->WriteAVPacket(Buffer, Size);
236 }
237 
243 QString TorcMuxer::GetAVCCodec(const QByteArray &Packet)
244 {
245  int size = Packet.size();
246  if (size < 7) // 3 (or 4) byte start code, 1 byte NALU, 1 byte IDC, 1 byte constraints and 1 byte level
247  {
248  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Cannot retrieve AVC1 codec data from packet - too short"));
249  return QString();
250  }
251 
252  bool found = false;
253  int index = 0;
254  for (; index < 2; index++)
255  {
256  if (Packet[index] == 0 && Packet[index + 1] == 0 && Packet[index + 2] == 1)
257  {
258  found = true;
259  break;
260  }
261  }
262 
263  if (!found)
264  {
265  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to find start code"));
266  return QString();
267  }
268 
269  index += 3;
270  if ((Packet[index] & 0x1f) == 7) // SPS NAL UNIT
271  return QStringLiteral("avc1.%1%2%3").arg(Packet[index + 1], 2, 16, QChar('0')).arg(Packet[index + 2], 2, 16, QChar('0')).arg(Packet[index + 3], 2, 16, QChar('0'));
272 
273  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to find SPS"));
274  return QString();
275 }
276 
277 int TorcMuxer::WriteAVPacket(uint8_t *Buffer, int Size)
278 {
279  if (!Buffer || Size < 1)
280  return -1;
281 
282  if (m_ringBuffer)
283  return m_ringBuffer->Write(Buffer, Size);
284 
285  return -1;
286 }
287 
289 {
290  return m_formatCtx && m_formatCtx->nb_streams > 0 && m_created;
291 }
292 
294 {
295  if (m_formatCtx == nullptr)
296  return -1;
297 
298  AVStream *audiostream = avformat_new_stream(m_formatCtx, nullptr);
299  if (!audiostream)
300  return -1;
301 
302  m_audioStream = m_formatCtx->nb_streams - 1;
303  audiostream->id = m_formatCtx->nb_streams - 1;
304  audiostream->codecpar->codec_id = AV_CODEC_ID_AAC;
305  audiostream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
306  audiostream->codecpar->codec_tag = 0;
307  audiostream->codecpar->bit_rate = 64000;
308  audiostream->codecpar->sample_rate = 44100;
309  audiostream->codecpar->channels = 2;
310  audiostream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
311 
312  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Audio stream id %1").arg(audiostream->id));
313  AVCodec* codec = avcodec_find_encoder(audiostream->codecpar->codec_id);
314  if (!codec)
315  {
316  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to find dummy audio codec"));
317  return -1;
318  }
319  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Found audio codec '%1'").arg(codec->name));
320 
321  m_audioContext = avcodec_alloc_context3(codec);
322  if (!m_audioContext)
323  {
324  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to create audio context"));
325  return -1;
326  }
327  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Created audio context"));
328 
329  avcodec_parameters_to_context(m_audioContext, audiostream->codecpar);
330  m_audioContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
331  if (m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER)
332  m_audioContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
333  if (avcodec_open2(m_audioContext, codec, nullptr) < 0)
334  {
335  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open codec '%1'").arg(codec->name));
336  return -1;
337  }
338 
339  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Dummy audio: frame size %1 sample_fmt %2 sample_rate %3 bitrate %4 channels %5")
340  .arg(m_audioContext->frame_size).arg(m_audioContext->sample_fmt).arg(m_audioContext->sample_rate)
341  .arg(m_audioContext->bit_rate).arg(m_audioContext->channels));
342 
343  m_audioPacket = av_packet_alloc();
344  m_audioFrame = av_frame_alloc();
345  m_audioFrame->nb_samples = m_audioContext->frame_size;
346  m_audioFrame->format = m_audioContext->sample_fmt;
347  m_audioFrame->channel_layout = m_audioContext->channel_layout;
348  CopyExtraData(m_audioContext->extradata_size, m_audioContext->extradata, m_audioStream);
349  if (av_frame_get_buffer(m_audioFrame, 0) < 0)
350  {
351  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to create audio frame buffers"));
352  return -1;
353  }
354  return audiostream->id;
355 }
356 
357 void TorcMuxer::WriteDummyAudio(void)
358 {
359  if (!m_audioContext || !m_audioFrame || !m_audioPacket || m_lastVideoPts == AV_NOPTS_VALUE || !m_formatCtx)
360  return;
361 
362  while (1)
363  {
364  if (avcodec_send_frame(m_audioContext, m_audioFrame) < 0)
365  {
366  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending frame to audio encoder"));
367  break;
368  }
369  else
370  {
371  int rec = avcodec_receive_packet(m_audioContext, m_audioPacket);
372  if (rec == AVERROR(EAGAIN))
373  continue;
374  if (rec < 0)
375  {
376  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error receiving packet from audio encoder"));
377  break;
378  }
379  while ((m_lastVideoPts > m_lastAudioPts) || m_lastAudioPts == AV_NOPTS_VALUE)
380  {
381  int64_t duration = ((float)m_audioContext->frame_size / 44100.0) * 90000;
382  m_lastAudioPts = m_lastAudioPts == AV_NOPTS_VALUE ? m_lastVideoPts : m_lastAudioPts + duration;
383  m_audioPacket->pts = m_lastAudioPts;
384  m_audioPacket->dts = m_lastAudioPts;
385  m_audioPacket->stream_index = m_audioStream;
386  if (av_write_frame(m_formatCtx, m_audioPacket) < 0)
387  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to write audio frame to muxer"));
388  }
389  av_packet_unref(m_audioPacket);
390  break;
391  }
392  }
393 }
394 
395 int TorcMuxer::AddH264Stream(int Width, int Height, int Profile, int Bitrate)
396 {
397  if (!m_formatCtx)
398  return -1;
399 
400  AVStream *h264video = avformat_new_stream(m_formatCtx, nullptr);
401  if (!h264video)
402  return -1;
403 
404  h264video->id = m_formatCtx->nb_streams - 1;
405  h264video->codecpar->codec_id = AV_CODEC_ID_H264;
406  h264video->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
407  h264video->codecpar->codec_tag = 0;
408  h264video->codecpar->bit_rate = Bitrate;
409  h264video->codecpar->profile = Profile;
410  h264video->codecpar->level = 30; // does this need to be set properly?
411  h264video->codecpar->format = AV_PIX_FMT_YUV420P;
412  h264video->codecpar->width = Width;
413  h264video->codecpar->height = Height;
414  return h264video->id;
415 }
416 
417 void TorcMuxer::CopyExtraData(int Size, void* Source, int Stream)
418 {
419  if (!m_formatCtx || !Source || Size < 1)
420  return;
421 
422  if (m_formatCtx->nb_streams <= 0 || (uint)Stream >= m_formatCtx->nb_streams || Stream < 0)
423  {
424  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Cannot copy extradata - invalid stream"));
425  return;
426  }
427 
428  AVStream *stream = m_formatCtx->streams[Stream];
429  stream->codecpar->extradata = (uint8_t*)av_mallocz(Size + AV_INPUT_BUFFER_PADDING_SIZE);
430  stream->codecpar->extradata_size = Size;
431  memcpy(stream->codecpar->extradata, Source, Size);
432 }
433 
434 bool TorcMuxer::AddPacket(AVPacket *Packet, bool CodecConfig)
435 {
436  if (!m_formatCtx || !m_created || !Packet)
437  return false;
438 
439  if (CodecConfig)
440  {
441  CopyExtraData(Packet->size, Packet->data, Packet->stream_index);
442 
443  // and start muxer if needed
444  if (!m_started)
445  {
446  Start();
447  m_started = true;
448  }
449  }
450 
451  if (!m_started)
452  {
453  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Ignoring packet - stream not started (waiting for config?)"));
454  return true;
455  }
456 
457  int result = av_write_frame(m_formatCtx, Packet);
458  if (result < 0)
459  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to write video frame"));
460  else
461  m_lastVideoPts = Packet->pts;
462 
463  WriteDummyAudio();
464  return result >= 0;
465 }
466 
468 {
469  if (m_formatCtx)
470  {
471  av_write_frame(m_formatCtx, nullptr);
472  if (m_ringBuffer)
473  m_ringBuffer->FinishSegment(Init);
474  }
475 }
476 
477 void TorcMuxer::Start(void)
478 {
479  if (m_formatCtx)
480  {
481  av_dump_format(m_formatCtx, 0, "stdout", 1);
482  AVDictionary *opts = nullptr;
483  av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
484  if (avformat_write_header(m_formatCtx, &opts))
485  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to set demuxer options"));
486  av_dict_free(&opts);
487  }
488 }
489 
491 {
492  if (m_audioContext && m_audioPacket)
493  {
494  // flush
495  avcodec_send_frame(m_audioContext, nullptr);
496  while (avcodec_receive_packet(m_audioContext, m_audioPacket) >= 0)
497  av_packet_unref(m_audioPacket);
498  }
499 
500  if (m_formatCtx)
501  {
502  if (av_write_trailer(m_formatCtx))
503  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to write stream trailer"));
504  //avio_close(m_formatCtx->pb);
505  }
506 }
507 
508 /* vim: set expandtab tabstop=4 shiftwidth=4: */
void Finish(void)
Definition: torcmuxer.cpp:490
int AddH264Stream(int Width, int Height, int Profile, int Bitrate)
Definition: torcmuxer.cpp:395
#define FFMPEG_BUFFER_SIZE
Definition: torcmuxer.cpp:36
int AddDummyAudioStream(void)
Definition: torcmuxer.cpp:293
bool IsValid(void)
Definition: torcmuxer.cpp:288
static QString GetAVCCodec(const QByteArray &Packet)
Determine the 3 byte H.264 codec descriptor string.
Definition: torcmuxer.cpp:243
bool AddPacket(AVPacket *Packet, bool CodecConfig)
Definition: torcmuxer.cpp:434
void FinishSegment(bool Init)
Definition: torcmuxer.cpp:467
int FinishSegment(bool Init)
Finish the current segment and start a new one.
int WriteAVPacket(uint8_t *Buffer, int Size)
Definition: torcmuxer.cpp:277
static int AVWritePacket(void *Opaque, uint8_t *Buffer, int Size)
Definition: torcmuxer.cpp:229
#define VERBOSE_LEVEL_NONE
Definition: torclogging.h:16
int Write(QByteArray *Data, int Size)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
static void TorcAVLog(void *Ptr, int Level, const char *Fmt, va_list VAL)
Definition: torcmuxer.cpp:38
TorcMuxer(const QString &File)
Definition: torcmuxer.cpp:121
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: torclogging.h:17