@0biwank/screen-capture 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,462 +0,0 @@
1
- #include "HLSMuxer/VideoEncoder.h"
2
- #include <iostream>
3
- #include <sstream>
4
- #include <iomanip>
5
- #include <string>
6
-
7
- extern "C"
8
- {
9
- #include <libavcodec/avcodec.h>
10
- #include <libavformat/avformat.h>
11
- #include <libswscale/swscale.h>
12
- #include <libavutil/opt.h>
13
- #include <libavutil/imgutils.h>
14
- #include <libavutil/frame.h>
15
- }
16
-
17
- VideoEncoder::VideoEncoder()
18
- {
19
- m_codecContext = nullptr;
20
- m_codec = nullptr;
21
- m_frame = nullptr;
22
- m_swsContext = nullptr;
23
- m_formatContext = nullptr;
24
- m_stream = nullptr;
25
-
26
- m_frameCount = 0;
27
- m_pts = 0;
28
- m_dts = 0;
29
- m_lastInputPts = -1;
30
- currentChunkIndex = 0;
31
-
32
- m_initialized = false;
33
- isPaused = false;
34
- }
35
-
36
- VideoEncoder::~VideoEncoder()
37
- {
38
- cleanup();
39
- while (!m_packetQueue.empty())
40
- {
41
- AVPacket *pkt = m_packetQueue.front();
42
- m_packetQueue.pop();
43
- av_packet_free(&pkt);
44
- }
45
- }
46
-
47
- bool VideoEncoder::initialize(int width, int height, int fps, int bitrate,
48
- OutputMode outputMode, int gopSize)
49
- {
50
- m_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
51
- if (!m_codec)
52
- {
53
- std::cerr << "H264 encoder not found!" << std::endl;
54
- return false;
55
- }
56
- m_codecContext = avcodec_alloc_context3(m_codec);
57
- if (!m_codecContext)
58
- {
59
- std::cerr << "Could not allocate codec context" << std::endl;
60
- return false;
61
- }
62
- m_codecContext->bit_rate = bitrate;
63
- m_codecContext->width = width;
64
- m_codecContext->height = height;
65
- m_codecContext->time_base = {1, fps};
66
- m_codecContext->framerate = {fps, 1};
67
- m_codecContext->gop_size = gopSize > 0 ? gopSize : fps;
68
- m_codecContext->max_b_frames = 0;
69
- m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
70
- this->frameWidth = width;
71
- this->frameHeight = height;
72
- this->frameRate = fps;
73
- this->mode = outputMode;
74
-
75
- AVDictionary *codecOptions = nullptr;
76
- if (m_codec && m_codec->name && std::string(m_codec->name) == "libx264")
77
- {
78
- av_dict_set(&codecOptions, "preset", "veryfast", 0);
79
- av_dict_set(&codecOptions, "tune", "zerolatency", 0);
80
- }
81
-
82
- int ret = avcodec_open2(m_codecContext, m_codec, &codecOptions);
83
- av_dict_free(&codecOptions);
84
- if (ret < 0)
85
- {
86
- std::cerr << "Could not open codec" << std::endl;
87
- cleanup();
88
- return false;
89
- }
90
- m_swsContext = sws_getContext(width, height, AV_PIX_FMT_BGRA, width, height,
91
- AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr,
92
- nullptr, nullptr);
93
- if (!m_swsContext)
94
- {
95
- std::cerr << "Could not initialize sws context" << std::endl;
96
- cleanup();
97
- return false;
98
- }
99
- m_frame = av_frame_alloc();
100
- if (!m_frame)
101
- {
102
- cleanup();
103
- return false;
104
- }
105
- m_frame->format = m_codecContext->pix_fmt;
106
- m_frame->width = width;
107
- m_frame->height = height;
108
-
109
- ret = av_frame_get_buffer(m_frame, 0);
110
- if (ret < 0)
111
- {
112
- std::cerr << "Could not allocate frame buffer" << std::endl;
113
- cleanup();
114
- return false;
115
- }
116
-
117
- if (outputMode == OutputMode::STANDALONE)
118
- {
119
- // Initialize format context for TS output
120
- avformat_alloc_output_context2(&m_formatContext, nullptr, "mpegts", nullptr);
121
- if (!m_formatContext)
122
- {
123
- std::cerr << "Could not create output context for TS" << std::endl;
124
- cleanup();
125
- return false;
126
- }
127
-
128
- // Create video stream in the TS container
129
- m_stream = avformat_new_stream(m_formatContext, nullptr);
130
- if (!m_stream)
131
- {
132
- std::cerr << "Could not create video stream" << std::endl;
133
- cleanup();
134
- return false;
135
- }
136
-
137
- // Copy codec parameters to stream
138
- avcodec_parameters_from_context(m_stream->codecpar, m_codecContext);
139
- m_stream->time_base = m_codecContext->time_base;
140
-
141
- targetChunkDuration = 10.0; // 10 second chunks by default
142
- currentSegmentStartPts = 0;
143
- currentSegmentDuration = 0.0;
144
- }
145
-
146
- m_initialized = true;
147
- return true;
148
- }
149
-
150
- bool VideoEncoder::encodeFrame(uint8_t *pixelData, int stride, int64_t pts)
151
- {
152
- if (!m_initialized)
153
- {
154
- std::cerr << "Encoder not initalized!" << std::endl;
155
- return false;
156
- }
157
-
158
- int ret = av_frame_make_writable(m_frame);
159
- if (ret < 0)
160
- {
161
- std::cerr << "Frame not writeable" << std::endl;
162
- return false;
163
- }
164
-
165
- const uint8_t *srcData[4] = {pixelData, nullptr, nullptr, nullptr};
166
- int srcLinesize[4] = {stride, 0, 0, 0};
167
- sws_scale(
168
- m_swsContext,
169
- srcData,
170
- srcLinesize,
171
- 0,
172
- frameHeight,
173
- m_frame->data,
174
- m_frame->linesize);
175
-
176
- if (pts == -1)
177
- {
178
- m_frame->pts = m_frameCount;
179
- }
180
- else
181
- {
182
- m_frame->pts = pts;
183
- }
184
-
185
- if (m_frame->pts <= m_lastInputPts)
186
- {
187
- m_frame->pts = m_lastInputPts + 1;
188
- }
189
- m_lastInputPts = m_frame->pts;
190
-
191
- m_pts = m_frame->pts;
192
-
193
- ret = avcodec_send_frame(m_codecContext, m_frame);
194
- // Reset forced keyframe flag after submission so only one frame gets the IDR hint.
195
- m_frame->pict_type = AV_PICTURE_TYPE_NONE;
196
- m_frame->flags &= ~AV_FRAME_FLAG_KEY;
197
- if (ret < 0)
198
- {
199
- std::cerr << "Error sending frame to encode" << std::endl;
200
- return false;
201
- }
202
-
203
- while (ret >= 0)
204
- {
205
- AVPacket *packet = av_packet_alloc();
206
- ret = avcodec_receive_packet(m_codecContext, packet);
207
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
208
- {
209
- av_packet_free(&packet);
210
- break;
211
- }
212
-
213
- if (mode == OutputMode::MUXED)
214
- {
215
- m_packetQueue.push(packet);
216
- }
217
- else if (mode == OutputMode::STANDALONE)
218
- {
219
- // Check if we need to start a new chunk
220
- if (shouldStartNewChunk() || currentChunkIndex == 0)
221
- {
222
- // Close current chunk if exists
223
- if (m_formatContext && m_formatContext->pb)
224
- {
225
- av_write_trailer(m_formatContext);
226
- avio_closep(&m_formatContext->pb);
227
-
228
- currentSegmentDuration = (m_pts - currentSegmentStartPts) * av_q2d(m_codecContext->time_base);
229
- }
230
-
231
- // Generate filename for new chunk
232
- std::ostringstream filename;
233
- filename << "video_chunk" << std::setfill('0') << std::setw(3) << currentChunkIndex << ".ts";
234
- std::string chunkFilename = filename.str();
235
- m_chunkFileNames.push_back(chunkFilename);
236
- std::cout << "Creating video chunk: " << chunkFilename << std::endl;
237
-
238
- // Open new chunk file
239
- if (avio_open(&m_formatContext->pb, chunkFilename.c_str(), AVIO_FLAG_WRITE) < 0)
240
- {
241
- std::cerr << "Could not open output file: " << chunkFilename << std::endl;
242
- av_packet_free(&packet);
243
- break;
244
- }
245
-
246
- // Write header for new chunk
247
- if (avformat_write_header(m_formatContext, nullptr) < 0)
248
- {
249
- std::cerr << "Could not write header for chunk: " << chunkFilename << std::endl;
250
- av_packet_free(&packet);
251
- break;
252
- }
253
-
254
- currentChunkIndex++;
255
- currentSegmentStartPts = m_pts;
256
-
257
- // Force keyframe at chunk boundaries
258
- forceKeyFrame();
259
- }
260
-
261
- // Write packet to current chunk
262
- packet->stream_index = m_stream->index;
263
- av_packet_rescale_ts(packet, m_codecContext->time_base, m_stream->time_base);
264
- av_interleaved_write_frame(m_formatContext, packet);
265
- av_packet_free(&packet);
266
- }
267
- }
268
- m_frameCount++;
269
- return true;
270
- }
271
-
272
- void VideoEncoder::cleanup()
273
- {
274
- std::cout << "VideoEncoder::cleanup() called, mode=" << (int)mode << ", initialized=" << m_initialized << std::endl;
275
-
276
- // Flush encoder to get remaining packets and finalize last segment
277
- if (m_initialized && m_codecContext)
278
- {
279
- std::cout << "Flushing encoder..." << std::endl;
280
- // Send NULL frame to flush encoder
281
- int flushRet = avcodec_send_frame(m_codecContext, nullptr);
282
- std::cout << "Flush send returned: " << flushRet << std::endl;
283
-
284
- // Receive all remaining packets
285
- while (true)
286
- {
287
- AVPacket *packet = av_packet_alloc();
288
- int ret = avcodec_receive_packet(m_codecContext, packet);
289
-
290
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
291
- {
292
- av_packet_free(&packet);
293
- break;
294
- }
295
-
296
- if (ret < 0)
297
- {
298
- av_packet_free(&packet);
299
- break;
300
- }
301
-
302
- if (mode == OutputMode::MUXED)
303
- {
304
- m_packetQueue.push(packet);
305
- }
306
- else if (mode == OutputMode::STANDALONE && m_formatContext)
307
- {
308
- // Open first chunk if not already open
309
- if (!m_formatContext->pb && currentChunkIndex == 0)
310
- {
311
- std::ostringstream filename;
312
- filename << "video_chunk" << std::setfill('0') << std::setw(3) << currentChunkIndex << ".ts";
313
- std::string chunkFilename = filename.str();
314
- m_chunkFileNames.push_back(chunkFilename);
315
- std::cout << "Flush: Creating first video chunk: " << chunkFilename << std::endl;
316
-
317
- if (avio_open(&m_formatContext->pb, chunkFilename.c_str(), AVIO_FLAG_WRITE) < 0)
318
- {
319
- std::cerr << "Could not open output file: " << chunkFilename << std::endl;
320
- av_packet_free(&packet);
321
- continue;
322
- }
323
-
324
- if (avformat_write_header(m_formatContext, nullptr) < 0)
325
- {
326
- std::cerr << "Flush: Could not write header for output file" << std::endl;
327
- av_packet_free(&packet);
328
- continue;
329
- }
330
-
331
- currentChunkIndex++;
332
- currentSegmentStartPts = m_pts;
333
- }
334
-
335
- if (m_formatContext->pb)
336
- {
337
- std::cout << "Flush: Writing packet to STANDALONE" << std::endl;
338
- packet->stream_index = m_stream->index;
339
- av_packet_rescale_ts(packet, m_codecContext->time_base, m_stream->time_base);
340
- av_interleaved_write_frame(m_formatContext, packet);
341
- }
342
- av_packet_free(&packet);
343
- }
344
- else
345
- {
346
- av_packet_free(&packet);
347
- }
348
- }
349
-
350
- // Finalize last segment if in STANDALONE mode
351
- if (mode == OutputMode::STANDALONE && m_formatContext && m_formatContext->pb)
352
- {
353
- // Update duration for the final segment
354
- currentSegmentDuration = (m_pts - currentSegmentStartPts) * av_q2d(m_codecContext->time_base);
355
-
356
- av_write_trailer(m_formatContext);
357
- avio_closep(&m_formatContext->pb);
358
- }
359
- }
360
-
361
- if (mode != OutputMode::MUXED)
362
- {
363
- while (!m_packetQueue.empty())
364
- {
365
- AVPacket *pkt = m_packetQueue.front();
366
- m_packetQueue.pop();
367
- av_packet_free(&pkt);
368
- }
369
- }
370
- if (m_frame)
371
- {
372
- av_frame_free(&m_frame);
373
- m_frame = nullptr;
374
- }
375
- if (m_swsContext)
376
- {
377
- sws_freeContext(m_swsContext);
378
- m_swsContext = nullptr;
379
- }
380
- if (m_codecContext)
381
- {
382
- avcodec_free_context(&m_codecContext);
383
- m_codecContext = nullptr;
384
- }
385
- if (m_formatContext)
386
- {
387
- if (m_formatContext->pb)
388
- {
389
- // Should already be closed above, but ensure it's closed
390
- avio_closep(&m_formatContext->pb);
391
- }
392
- avformat_free_context(m_formatContext);
393
- m_formatContext = nullptr;
394
- }
395
- m_initialized = false;
396
- }
397
-
398
- bool VideoEncoder::hasPackets() const
399
- {
400
- return !m_packetQueue.empty();
401
- }
402
-
403
- AVPacket *VideoEncoder::getNextPacket()
404
- {
405
- if (m_packetQueue.empty())
406
- {
407
- return nullptr;
408
- }
409
-
410
- AVPacket *packet = m_packetQueue.front();
411
- m_packetQueue.pop();
412
- return packet;
413
- }
414
-
415
- void VideoEncoder::forceKeyFrame()
416
- {
417
- if (m_frame && m_codecContext)
418
- {
419
- m_frame->pict_type = AV_PICTURE_TYPE_I;
420
- m_frame->flags |= AV_FRAME_FLAG_KEY;
421
- }
422
- }
423
-
424
- bool VideoEncoder::shouldStartNewChunk() const
425
- {
426
- double duration = (m_pts - currentSegmentStartPts) * av_q2d(m_codecContext->time_base);
427
- return duration >= targetChunkDuration;
428
- }
429
-
430
- std::string VideoEncoder::generatePlaylist() const
431
- {
432
- if (mode != OutputMode::STANDALONE || m_chunkFileNames.empty())
433
- {
434
- return "";
435
- }
436
-
437
- std::string playlist = "#EXTM3U\n";
438
- playlist += "#EXT-X-VERSION:3\n";
439
- playlist += "#EXT-X-TARGETDURATION:" + std::to_string(static_cast<int>(targetChunkDuration + 1)) + "\n";
440
- playlist += "#EXT-X-MEDIA-SEQUENCE:0\n";
441
-
442
- for (size_t i = 0; i < m_chunkFileNames.size(); ++i)
443
- {
444
- // Calculate actual duration for each segment
445
- double duration = targetChunkDuration; // Default duration
446
- if (i == m_chunkFileNames.size() - 1 && currentSegmentDuration > 0)
447
- {
448
- duration = currentSegmentDuration; // Last segment might be shorter
449
- }
450
-
451
- playlist += "#EXTINF:" + std::to_string(duration) + ",\n";
452
- playlist += m_chunkFileNames[i] + "\n";
453
- }
454
-
455
- // Add end list marker if encoding is complete
456
- if (!m_initialized)
457
- {
458
- playlist += "#EXT-X-ENDLIST\n";
459
- }
460
-
461
- return playlist;
462
- }