FFmpegfs Fuse Multi Media Filesystem 2.19
Loading...
Searching...
No Matches
transcode.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2006-2008 David Collett
3 * Copyright (C) 2008-2013 K. Henriksson
4 * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * On Debian systems, the complete text of the GNU General Public License
21 * Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
22 */
23
36#include "transcode.h"
37#include "ffmpegfs.h"
38#include "ffmpeg_transcoder.h"
39#include "buffer.h"
40#include "cache.h"
41#include "logging.h"
42#include "cache_entry.h"
43#include "thread_pool.h"
44
45#include <unistd.h>
46#include <atomic>
47#include <chrono>
48#include <cstdio>
49
50const int GRANULARITY = 250;
51const int FRAME_TIMEOUT = 60;
64
65static std::unique_ptr<Cache> cache;
66static std::atomic_bool thread_exit;
68static bool transcode(std::shared_ptr<THREAD_DATA> thread_data, Cache_Entry *cache_entry, FFmpeg_Transcoder & transcoder, bool *timeout);
69static int transcoder_thread(std::shared_ptr<THREAD_DATA> thread_data);
70static int start_transcoder_thread(Cache_Entry* cache_entry);
71static bool transcode_until(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no);
72static int transcode_finish(Cache_Entry* cache_entry, FFmpeg_Transcoder & transcoder);
73static void reopen_finished_incomplete_cache(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name);
74static bool cached_item_available(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no);
75static bool invalidate_stale_cache_file(Cache_Entry* cache_entry, uint32_t segment_no, uint32_t item_no, const char* item_name);
76
87static bool transcode_until(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no)
88{
89 bool success = false;
90
91 if (cached_item_available(cache_entry, offset, len, segment_no))
92 {
93 return true;
94 }
95
96 try
97 {
98 // Wait until decoder thread has reached the desired position
99 if (cache_entry->m_is_decoding)
100 {
101 bool reported = false;
102 while (!cached_item_available(cache_entry, offset, len, segment_no) && !cache_entry->m_cache_info.m_error)
103 {
104 if (fuse_interrupted())
105 {
106 Logging::info(cache_entry->virtname(), "The client has gone away.");
107 errno = 0; // No error
108 break;
109 }
110
111 if (thread_exit)
112 {
113 Logging::warning(cache_entry->virtname(), "Thread exit was received.");
114 errno = EINTR;
115 throw false;
116 }
117
118 if (!reported)
119 {
120 if (!segment_no)
121 {
122 Logging::trace(cache_entry->virtname(), "Cache miss at offset %1 with length %2.", offset, len);
123 }
124 else
125 {
126 Logging::trace(cache_entry->virtname(), "Cache miss at offset %1 with length %2 for segment no. %3.", offset, len, segment_no);
127 }
128 reported = true;
129 }
130 mssleep(250);
131 }
132
133 if (reported)
134 {
135 if (!segment_no)
136 {
137 Logging::trace(cache_entry->virtname(), "Cache hit at offset %1 with length %2.", offset, len);
138 }
139 else
140 {
141 Logging::trace(cache_entry->virtname(), "Cache hit at offset %1 with length %2 for segment no. %3.", offset, len, segment_no);
142 }
143 }
144 success = !cache_entry->m_cache_info.m_error && cached_item_available(cache_entry, offset, len, segment_no);
145 }
146 }
147 catch (bool _success)
148 {
149 success = _success;
150 }
151
152 return success;
153}
154
161static int transcode_finish(Cache_Entry* cache_entry, FFmpeg_Transcoder & transcoder)
162{
163 int res = transcoder.encode_finish();
164 if (res < 0)
165 {
166 return res;
167 }
168
169 // Check encoded buffer size. Does not affect HLS segments.
170 cache_entry->m_cache_info.m_duration = transcoder.duration();
171 cache_entry->m_cache_info.m_encoded_filesize = cache_entry->m_buffer->buffer_watermark();
172 cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count();
173 cache_entry->m_cache_info.m_segment_count = transcoder.segment_count();
175 cache_entry->m_is_decoding = false;
176 cache_entry->m_cache_info.m_errno = 0;
177 cache_entry->m_cache_info.m_averror = 0;
178
179 Logging::debug(transcoder.virtname(), "Finishing file.");
180
181 if (!cache_entry->m_buffer->reserve(cache_entry->m_cache_info.m_encoded_filesize))
182 {
183 Logging::debug(transcoder.virtname(), "Unable to truncate the buffer.");
184 }
185
186 if (!transcoder.is_multiformat())
187 {
188 Logging::debug(transcoder.virtname(), "Predicted size: %1 Final: %2 Diff: %3 (%4%).",
190 format_size_ex(cache_entry->m_cache_info.m_encoded_filesize).c_str(),
192 ((static_cast<double>(cache_entry->m_cache_info.m_encoded_filesize) * 1000 / (static_cast<double>(cache_entry->m_cache_info.m_predicted_filesize) + 1)) + 5) / 10);
193 }
194
195 cache_entry->flush();
196
197 return 0;
198}
199
214static void reopen_finished_incomplete_cache(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name)
215{
216 if (cache_entry == nullptr || !cache_entry->is_finished_incomplete())
217 {
218 return;
219 }
220
221 Logging::warning(cache_entry->virtname(),
222 "Re-opening incomplete cache to generate missing %1 no. %2.",
223 item_name,
224 item_no);
225
227 cache_entry->m_cache_info.m_error = false;
228 cache_entry->m_cache_info.m_errno = 0;
229 cache_entry->m_cache_info.m_averror = 0;
230}
231
246static bool invalidate_stale_cache_file(Cache_Entry* cache_entry, uint32_t segment_no, uint32_t item_no, const char* item_name)
247{
248 if (cache_entry == nullptr || cache_entry->m_buffer == nullptr)
249 {
250 errno = EINVAL;
251 return false;
252 }
253
254 const std::string cachefile = cache_entry->m_buffer->cachefile(segment_no);
255
256 if (item_no)
257 {
258 Logging::warning(cache_entry->virtname(),
259 "Cached %1 no. %2 is marked available, but cache file '%3' is missing or empty. Recreating it.",
260 item_name,
261 item_no,
262 cachefile.c_str());
263 }
264 else
265 {
266 Logging::warning(cache_entry->virtname(),
267 "Cached file is marked available, but cache file '%1' is missing or empty. Recreating it.",
268 cachefile.c_str());
269 }
270
271 cache_entry->m_buffer->invalidate_segment(segment_no);
272
274 cache_entry->m_cache_info.m_error = false;
275 cache_entry->m_cache_info.m_errno = 0;
276 cache_entry->m_cache_info.m_averror = 0;
277 cache_entry->m_suspend_timeout = false;
278
279 if (!segment_no)
280 {
281 cache_entry->m_cache_info.m_encoded_filesize = 0;
282 }
283
284 const FFmpegfs_Format *current_format = params.current_format(cache_entry->virtualfile());
285 if (item_no && current_format != nullptr && current_format->is_multiformat())
286 {
287 cache_entry->m_seek_to_no = item_no;
288 }
289
290 return false;
291}
292
306static bool cached_item_available(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no)
307{
308 if (cache_entry == nullptr || cache_entry->m_buffer == nullptr)
309 {
310 errno = EINVAL;
311 return false;
312 }
313
314 const size_t end = offset + len;
315 const bool logically_available =
316 cache_entry->is_finished() ||
317 (segment_no && cache_entry->m_buffer->is_segment_finished(segment_no)) ||
318 (cache_entry->m_buffer->tell(segment_no) >= end);
319
320 if (!logically_available)
321 {
322 return false;
323 }
324
325 if (cache_entry->m_buffer->cachefile_valid(segment_no))
326 {
327 return true;
328 }
329
330 return invalidate_stale_cache_file(cache_entry,
331 segment_no,
332 segment_no,
333 segment_no ? "segment" : "file");
334}
335
336void transcoder_cache_path(std::string * path)
337{
338 if (params.m_cachepath.size())
339 {
340 *path = params.m_cachepath;
341 }
342 else
343 {
344 if (geteuid() == 0)
345 {
346 // Running as root
347 *path = "/var/cache";
348 }
349 else
350 {
351 // Running as regular user, put cache in home dir
352 if (const char* cache_home = std::getenv("XDG_CACHE_HOME"))
353 {
354 *path = cache_home;
355 }
356 else
357 {
358 expand_path(path, "~/.cache");
359 }
360 }
361 }
362
363 append_sep(path);
364
365 *path += PACKAGE;
366
367 append_sep(path);
368}
369
371{
372 if (cache == nullptr)
373 {
374 Logging::debug(nullptr, "Creating new media file cache.");
375 cache = std::make_unique<Cache>();
376 if (cache == nullptr)
377 {
378 Logging::error(nullptr, "Unable to create new media file cache. Out of memory.");
379 std::fprintf(stderr, "ERROR: Creating new media file cache. Out of memory.\n");
380 return false;
381 }
382
383 if (!cache->load_index())
384 {
385 std::fprintf(stderr, "ERROR: Creating media file cache failed.\n");
386 return false;
387 }
388 }
389 return true;
390}
391
393{
394 if (cache != nullptr)
395 {
396 cache.reset();
397
398 Logging::debug(nullptr, "Deleting media file cache.");
399 }
400}
401
402bool transcoder_cached_filesize(LPVIRTUALFILE virtualfile, struct stat *stbuf)
403{
404 Cache_Entry* cache_entry = cache->openio(virtualfile);
405 if (cache_entry == nullptr)
406 {
407 return false;
408 }
409
410 size_t encoded_filesize = cache_entry->m_cache_info.m_encoded_filesize;
411
412 if (!encoded_filesize)
413 {
414 // If not yet encoded, return predicted file size
415 encoded_filesize = cache_entry->m_cache_info.m_predicted_filesize;
416 }
417
418 if (encoded_filesize)
419 {
420 stat_set_size(stbuf, encoded_filesize);
421 return true;
422 }
423 else
424 {
425 return false;
426 }
427}
428
429bool transcoder_set_filesize(LPVIRTUALFILE virtualfile, int64_t duration, BITRATE audio_bit_rate, int channels, int sample_rate, AVSampleFormat sample_format, BITRATE video_bit_rate, int width, int height, bool interleaved, const AVRational &framerate)
430{
431 Cache_Entry* cache_entry = cache->openio(virtualfile);
432 if (cache_entry == nullptr)
433 {
434 Logging::error(nullptr, "Out of memory getting file size.");
435 return false;
436 }
437
438 const FFmpegfs_Format *current_format = params.current_format(virtualfile);
439 if (current_format == nullptr)
440 {
441 Logging::error(cache_entry->virtname(), "Internal error getting file size.");
442 return false;
443 }
444
445 size_t filesize = 0;
446
447 if (!FFmpeg_Transcoder::audio_size(&filesize, current_format->audio_codec(), audio_bit_rate, duration, channels, sample_rate, sample_format))
448 {
449 Logging::warning(cache_entry->virtname(), "Unsupported audio codec '%1' for format %2.", get_codec_name(current_format->audio_codec()), current_format->desttype().c_str());
450 }
451
452 if (!FFmpeg_Transcoder::video_size(&filesize, current_format->video_codec(), video_bit_rate, duration, width, height, interleaved, framerate))
453 {
454 Logging::warning(cache_entry->virtname(), "Unsupported video codec '%1' for format %2.", get_codec_name(current_format->video_codec()), current_format->desttype().c_str());
455 }
456
457 if (!FFmpeg_Transcoder::total_overhead(&filesize, current_format->filetype()))
458 {
459 Logging::warning(cache_entry->virtname(), "Unsupported file type '%1' for format %2.", get_filetype_text(current_format->filetype()).c_str(), current_format->desttype().c_str());
460 }
461
462 cache_entry->m_cache_info.m_predicted_filesize = virtualfile->m_predicted_size = filesize;
463
464 Logging::trace(cache_entry->virtname(), "Predicted transcoded size of %1.", format_size_ex(cache_entry->m_cache_info.m_predicted_filesize).c_str());
465
466 return true;
467}
468
470{
471 FFmpeg_Transcoder transcoder;
472 bool success = false;
473
474 if (transcoder.open_input_file(virtualfile) >= 0)
475 {
476 if (cache_entry != nullptr)
477 {
478 cache_entry->m_cache_info.m_predicted_filesize = transcoder.predicted_filesize();
479 cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count();
480 cache_entry->m_cache_info.m_segment_count = transcoder.segment_count();
481 cache_entry->m_cache_info.m_duration = transcoder.duration();
482 }
483
484 Logging::trace(transcoder.filename(), "Predicted transcoded size of %1.", format_size_ex(transcoder.predicted_filesize()).c_str());
485
486 transcoder.closeio();
487
488 success = true;
489 }
490
491 return success;
492}
493
504static int start_transcoder_thread(Cache_Entry* cache_entry)
505{
506 if (cache_entry == nullptr)
507 {
508 return EINVAL;
509 }
510
511 bool expected_is_decoding = false;
512 if (!cache_entry->m_is_decoding.compare_exchange_strong(expected_is_decoding, true))
513 {
514 std::unique_lock<std::recursive_mutex> lock_active_mutex(cache_entry->m_active_mutex, std::try_to_lock);
515 if (!lock_active_mutex.owns_lock())
516 {
517 Logging::warning(cache_entry->virtname(), "Transcoder thread already running.");
518 return 0;
519 }
520
521 Logging::warning(cache_entry->virtname(),
522 "Transcoder thread was marked as running, but no active worker owns the cache entry. Resetting decoding state and starting a new worker.");
523
524 cache_entry->m_is_decoding = false;
525 expected_is_decoding = false;
526 if (!cache_entry->m_is_decoding.compare_exchange_strong(expected_is_decoding, true))
527 {
528 Logging::warning(cache_entry->virtname(), "Transcoder thread already running.");
529 return 0;
530 }
531 }
532
533 const FFmpegfs_Format *current_format = params.current_format(cache_entry->virtualfile());
534 if (current_format != nullptr &&
535 current_format->is_multiformat() &&
536 !cache_entry->virtualfile()->get_segment_count() &&
537 !cache_entry->m_cache_info.m_segment_count)
538 {
539 // HLS/frame-set buffers need a known segment/frame count before
540 // Buffer::init() can create the cache file table. With deferred
541 // transcoding this may not have happened during open(), so predict
542 // the source properties now, before the worker calls openio(true).
543 if (!transcoder_predict_filesize(cache_entry->virtualfile(), cache_entry))
544 {
545 int ret = errno;
546 if (!ret)
547 {
548 ret = EIO;
549 }
550
551 cache_entry->m_is_decoding = false;
552 return ret;
553 }
554 }
555
556 Logging::debug(cache_entry->filename(), "Starting transcoder thread.");
557
558 std::shared_ptr<THREAD_DATA> thread_data = std::make_shared<THREAD_DATA>();
559
560 thread_data->m_initialised = false;
561 thread_data->m_cache_entry = cache_entry;
562 thread_data->m_thread_running_lock_guard = false;
563
564 {
565 std::unique_lock<std::mutex> lock_thread_running_mutex(thread_data->m_thread_running_mutex);
566
567 tp->schedule_thread(std::bind(&transcoder_thread, thread_data));
568
569 // Let decoder get into gear before returning from open/read.
570 while (!thread_data->m_thread_running_lock_guard)
571 {
572 thread_data->m_thread_running_cond.wait(lock_thread_running_mutex);
573 }
574 }
575
576 if (cache_entry->m_cache_info.m_error)
577 {
578 int ret = cache_entry->m_cache_info.m_errno;
579 if (!ret)
580 {
581 ret = EIO;
582 }
583 return ret;
584 }
585
586 Logging::debug(cache_entry->filename(), "Transcoder thread is running.");
587 return 0;
588}
589
590Cache_Entry* transcoder_new(LPVIRTUALFILE virtualfile, bool begin_transcode)
591{
592 // Allocate transcoder structure
593 Cache_Entry* cache_entry = cache->openio(virtualfile);
594 if (cache_entry == nullptr)
595 {
596 return nullptr;
597 }
598
599 Logging::trace(cache_entry->filename(), "Creating transcoder object.");
600
601 try
602 {
603 cache_entry->lock();
604
605 const FFmpegfs_Format *current_format = params.current_format(virtualfile);
606 const bool create_cache = begin_transcode ||
607 (current_format != nullptr &&
608 current_format->is_multiformat() &&
609 virtualfile->get_segment_count() != 0);
610
611 if (!cache_entry->openio(create_cache))
612 {
613 throw static_cast<int>(errno);
614 }
615
617 {
618 // Disable cache
619 cache_entry->clear();
620 }
621 else if (!cache_entry->m_is_decoding && cache_entry->outdated())
622 {
623 cache_entry->clear();
624 }
625
626 if (cache_entry->m_cache_info.m_duration)
627 {
628 virtualfile->m_duration = cache_entry->m_cache_info.m_duration;
629 }
630 if (cache_entry->m_cache_info.m_predicted_filesize)
631 {
632 virtualfile->m_predicted_size = cache_entry->m_cache_info.m_predicted_filesize;
633 }
634 if (cache_entry->m_cache_info.m_video_frame_count)
635 {
636 virtualfile->m_video_frame_count = cache_entry->m_cache_info.m_video_frame_count;
637 }
638
639 if (!cache_entry->m_is_decoding && !cache_entry->is_finished_success())
640 {
641 if (begin_transcode)
642 {
643 int ret = start_transcoder_thread(cache_entry);
644 if (ret)
645 {
646 Logging::trace(cache_entry->filename(), "Transcoder error!");
647 throw ret;
648 }
649 }
650 else if (!cache_entry->m_cache_info.m_predicted_filesize)
651 {
652 if (!transcoder_predict_filesize(virtualfile, cache_entry))
653 {
654 throw static_cast<int>(errno);
655 }
656 }
657 }
658 else if (begin_transcode)
659 {
660 Logging::trace(cache_entry->virtname(), "Reading file from cache.");
661 }
662
663 cache_entry->unlock();
664 }
665 catch (int orgerrno)
666 {
667 cache_entry->m_is_decoding = false;
668 cache_entry->unlock();
669 cache->closeio(&cache_entry, CACHE_CLOSE_DELETE);
670 cache_entry = nullptr; // Make sure to return NULL here even if the cache could not be deleted now (still in use)
671 errno = orgerrno; // Restore last errno
672 }
673
674 return cache_entry;
675}
676
677bool transcoder_read(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, int * bytes_read, uint32_t segment_no)
678{
679 bool success = true;
680
681 if (!segment_no)
682 {
683 Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3.", len, offset, len + offset);
684 }
685 else
686 {
687 Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3 for segment no. %4.", len, offset, len + offset, segment_no);
688 }
689
690 // Store access time
691 cache_entry->update_access();
692
693 // Update read counter
694 cache_entry->update_read_count();
695
696 try
697 {
698 const bool segment_logically_complete = segment_no &&
699 (cache_entry->m_buffer->is_segment_finished(segment_no) || cache_entry->is_finished());
700 bool segment_complete = segment_logically_complete;
701 bool repair_requested = false;
702
703 if (segment_logically_complete && !cache_entry->m_buffer->cachefile_valid(segment_no))
704 {
705 invalidate_stale_cache_file(cache_entry, segment_no, segment_no, "segment");
706 segment_complete = false;
707 repair_requested = true;
708 }
709
710 if (!segment_no && cache_entry->is_finished() && !cache_entry->m_buffer->cachefile_valid(0))
711 {
712 invalidate_stale_cache_file(cache_entry, 0, 0, "file");
713 repair_requested = true;
714 }
715
716 const bool segment_missing = segment_no && !segment_complete;
717
718 // For HLS partial caches, an incomplete cache is still usable. Only
719 // start/restart the transcoder when the requested segment is not
720 // finished yet. Physical cache files are checked as well: if a segment
721 // is marked as finished in memory/the cache index but the actual file
722 // has been deleted or truncated, it is treated as missing and repaired.
723 if (segment_missing)
724 {
725 if (!cache_entry->m_is_decoding)
726 {
727 // Segment 1 naturally starts at the beginning. For later HLS
728 // segments, stack the target before the worker starts so a
729 // direct request does not first encode segment 1 and only seek
730 // afterwards.
731 cache_entry->m_seek_to_no = segment_no;
732
733 // A FINISHED_INCOMPLETE HLS cache may still contain useful
734 // segments, but a missing requested segment needs a fresh
735 // repair worker. Only re-open it when no worker is active; an
736 // active worker must keep its current cache state.
737 reopen_finished_incomplete_cache(cache_entry, segment_no, "HLS segment");
738 }
739 else
740 {
741 // A running HLS transcoder can satisfy large jumps by
742 // consuming m_seek_to_no in transcode(). However, during
743 // normal linear playback the current or immediately following
744 // segment is often requested before it has been finalised. Do
745 // not queue a seek for those normal sequential reads: doing so
746 // can make the encoder reopen the same segment and corrupt the
747 // cache file.
748 //
749 // Only queue a seek while the worker is active if the request
750 // is farther away than the configured minimum seek distance.
751 // The FFmpeg_Transcoder still performs its own final check and
752 // logs discarded short seeks.
753 const uint32_t current_segment = cache_entry->m_buffer->current_segment_no();
754 if (current_segment && segment_no > current_segment)
755 {
756 uint32_t min_seek_segments = 0;
758 {
759 min_seek_segments = static_cast<uint32_t>(params.m_min_seek_time_diff / params.m_segment_duration);
760 }
761
762 if (segment_no > current_segment + min_seek_segments)
763 {
764 cache_entry->m_seek_to_no = segment_no;
765 }
766 }
767 }
768 }
769
770 if (repair_requested ||
771 (!cache_entry->m_is_decoding &&
772 (segment_missing ||
773 (!cache_entry->is_finished_success() &&
774 !(cache_entry->is_finished_incomplete() && !segment_missing)))))
775 {
776 int ret = start_transcoder_thread(cache_entry);
777 if (ret)
778 {
779 errno = ret;
780 throw false;
781 }
782 }
783
784 if (!cache_entry->is_finished_success() && !cache_entry->is_finished_incomplete())
785 {
786 switch (params.current_format(cache_entry->virtualfile())->filetype())
787 {
788 case FILETYPE::MP3:
789 {
790 // If we are encoding to MP3 and the requested data overlaps the ID3v1 tag
791 // at the end of the file, do not encode data first up to that position.
792 // This optimises the case where applications read the end of the file
793 // first to read the ID3v1 tag.
794 if ((offset > cache_entry->m_buffer->tell(segment_no)) &&
795 (offset + len >= (cache_entry->size() - ID3V1_TAG_LENGTH)))
796 {
797 // Stuff buffer with garbage, apps won't try to play that chunk anyway.
798 std::memset(buff, 0xFF, len);
799 if (cache_entry->size() - offset == ID3V1_TAG_LENGTH)
800 {
801 std::memcpy(buff, &cache_entry->m_id3v1, std::min(len, ID3V1_TAG_LENGTH));
802 }
803
804 errno = 0;
805
806 throw true; // OK
807 }
808 break;
809 }
810 default:
811 {
812 break;
813 }
814 }
815
816 // Windows seems to access the files on Samba drives starting at the last 64K segment simply when
817 // the file is opened. Setting win_smb_fix=1 will ignore these attempts (not decode the file up
818 // to this point).
819 // Access will only be ignored if occurring at the second access.
820 if (params.m_win_smb_fix && cache_entry->read_count() == 2)
821 {
822 if ((offset > cache_entry->m_buffer->tell(segment_no)) &&
823 (len <= 65536) &&
824 check_ignore(cache_entry->size(), offset) &&
825 ((offset + len) > (cache_entry->size())))
826 {
827 Logging::warning(cache_entry->virtname(), "Ignoring Windows' groundless access to the last 8K boundary of the file.");
828
829 errno = 0;
830 *bytes_read = 0; // We've read nothing
831 len = 0;
832
833 throw true; // OK
834 }
835 }
836 }
837
838 success = transcode_until(cache_entry, offset, len, segment_no);
839
840 if (!success)
841 {
842 errno = cache_entry->m_cache_info.m_errno ? cache_entry->m_cache_info.m_errno : EIO;
843 throw false;
844 }
845
846 // Open for reading if necessary. This intentionally happens after
847 // transcode_until(), so a missing HLS segment is created by the
848 // transcoder, not by a read-only cache probe.
849 if (!cache_entry->m_buffer->open_file(segment_no, CACHE_FLAG_RO))
850 {
851 throw false;
852 }
853
854 // truncate if we didn't actually get len
855 if (cache_entry->m_buffer->buffer_watermark(segment_no) < offset)
856 {
857 len = 0;
858 }
859 else if (cache_entry->m_buffer->buffer_watermark(segment_no) < offset + len)
860 {
861 len = cache_entry->m_buffer->buffer_watermark(segment_no) - offset;
862 }
863
864 if (len)
865 {
866 if (!cache_entry->m_buffer->copy(reinterpret_cast<uint8_t*>(buff), offset, len, segment_no))
867 {
868 len = 0;
869 // We already capped len to not overread the buffer, so it is an error if we end here.
870 throw false;
871 }
872
873 if (cache_entry->m_cache_info.m_error)
874 {
875 errno = cache_entry->m_cache_info.m_errno ? cache_entry->m_cache_info.m_errno : EIO;
876 throw false;
877 }
878 }
879
880 errno = 0;
881 }
882 catch (bool _success)
883 {
884 success = _success;
885 }
886
887 *bytes_read = static_cast<int>(len);
888
889 return success;
890}
891
892bool transcoder_read_frame(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, uint32_t frame_no, int * bytes_read, LPVIRTUALFILE virtualfile)
893{
894 bool success = false;
895
896 Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3 for frame no. %4.", len, offset, len + offset, frame_no);
897
898 // Store access time
899 cache_entry->update_access();
900
901 // Update read counter
902 cache_entry->update_read_count();
903
904 try
905 {
906 if (cache_entry->is_finished() && !cache_entry->m_buffer->cachefile_valid(0))
907 {
908 invalidate_stale_cache_file(cache_entry, 0, frame_no, "frame-set cache");
909 cache_entry->m_seek_to_no = frame_no;
910 }
911
912 // Open for reading if necessary. For frame sets this only opens the
913 // frame index/cache for probing; a missing frame still has to start the
914 // transcoder below.
915 if (!cache_entry->m_buffer->open_file(0, CACHE_FLAG_RO))
916 {
917 throw false;
918 }
919
920 std::vector<uint8_t> data;
921
922 // Try to read the requested frame first. Frame-set files are opened
923 // through their parent object without starting the transcoder, because
924 // the exact frame number is only known here. Therefore a cache miss
925 // must start or wake the transcoder from this read path.
926 success = cache_entry->m_buffer->read_frame(&data, frame_no);
927 if (!success)
928 {
929 if (errno != EAGAIN)
930 {
931 Logging::error(cache_entry->virtname(), "Reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno));
932 throw false;
933 }
934
935 bool reported = false;
936
937 // Stack the requested frame before starting the worker so a direct
938 // frame-set access does not begin at frame 1 first.
939 cache_entry->m_seek_to_no = frame_no;
940
941 // A FINISHED_INCOMPLETE frame-set cache means that some frames are
942 // valid, but the requested missing frame still has to be generated.
943 // Clear the finished state before starting the repair worker,
944 // otherwise transcode() exits immediately.
945 reopen_finished_incomplete_cache(cache_entry, frame_no, "frame");
946
947 if (!cache_entry->m_is_decoding)
948 {
949 int ret = start_transcoder_thread(cache_entry);
950 if (ret)
951 {
952 errno = ret;
953 throw false;
954 }
955 }
956
957 int retries = TOTAL_RETRIES;
958 while (!cache_entry->m_buffer->read_frame(&data, frame_no) && !thread_exit)
959 {
960 if (errno != EAGAIN)
961 {
962 Logging::error(cache_entry->virtname(), "Reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno));
963 throw false;
964 }
965
966 // A previous frame-set worker may have reached EOF just after
967 // this request stacked m_seek_to_no. In that case the worker
968 // exits, the requested frame is still missing, and simply
969 // waiting would time out. Start a fresh repair worker for the
970 // same frame as soon as the old worker is no longer decoding.
971 if (!cache_entry->m_is_decoding)
972 {
973 cache_entry->m_seek_to_no = frame_no;
974 reopen_finished_incomplete_cache(cache_entry, frame_no, "frame");
975
976 int ret = start_transcoder_thread(cache_entry);
977 if (ret)
978 {
979 errno = ret;
980 throw false;
981 }
982
983 // The new worker is now responsible for producing the
984 // requested frame, so give it a full retry budget.
985 retries = TOTAL_RETRIES;
986 }
987
988 if (!cache_entry->m_suspend_timeout)
989 {
990 if (!--retries)
991 {
992 errno = ETIMEDOUT;
993 Logging::error(cache_entry->virtname(), "Timeout reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno));
994 throw false;
995 }
996 }
997 else
998 {
999 retries = TOTAL_RETRIES;
1000 }
1001
1002 if (thread_exit)
1003 {
1004 Logging::warning(cache_entry->virtname(), "Thread exit was received.");
1005 errno = EINTR;
1006 throw false;
1007 }
1008
1009 if (!reported)
1010 {
1011 Logging::trace(cache_entry->virtname(), "Frame no. %1: Cache miss at offset %<11zu>2 (length %<6u>3).", frame_no, offset, len);
1012 reported = true;
1013 }
1014
1016 }
1017
1018 if (thread_exit)
1019 {
1020 Logging::warning(cache_entry->virtname(), "Thread exit was received.");
1021 errno = EINTR;
1022 throw false;
1023 }
1024
1025 Logging::trace(cache_entry->virtname(), "Frame no. %1: Cache hit at offset %<11zu>2 (length %<6u>3).", frame_no, offset, len);
1026
1027 success = !cache_entry->m_cache_info.m_error;
1028 }
1029
1030 if (success)
1031 {
1032 if (data.size() < offset)
1033 {
1034 len = 0;
1035 }
1036 else if (data.size() < offset + len)
1037 {
1038 len = data.size() - offset;
1039 }
1040
1041 if (len)
1042 {
1043 std::memcpy(buff, data.data() + offset, len);
1044 }
1045
1046 stat_set_size(&virtualfile->m_st, data.size());
1047
1048 *bytes_read = static_cast<int>(len);
1049 }
1050 }
1051 catch (bool _success)
1052 {
1053 success = _success;
1054 }
1055
1056 return success;
1057}
1058
1060{
1061 cache->closeio(&cache_entry);
1062}
1063
1065{
1066 return cache_entry->size();
1067}
1068
1069size_t transcoder_buffer_watermark(Cache_Entry* cache_entry, uint32_t segment_no)
1070{
1071 return cache_entry->m_buffer->buffer_watermark(segment_no);
1072}
1073
1074size_t transcoder_buffer_tell(Cache_Entry* cache_entry, uint32_t segment_no)
1075{
1076 return cache_entry->m_buffer->tell(segment_no);
1077}
1078
1080{
1081 thread_exit = true;
1082}
1083
1085{
1086 if (cache != nullptr)
1087 {
1088 return cache->maintenance();
1089 }
1090 else
1091 {
1092 return false;
1093 }
1094}
1095
1097{
1098 if (cache != nullptr)
1099 {
1100 return cache->clear();
1101 }
1102 else
1103 {
1104 return false;
1105 }
1106}
1107
1116static bool transcode(std::shared_ptr<THREAD_DATA> thread_data, Cache_Entry *cache_entry, FFmpeg_Transcoder & transcoder, bool *timeout)
1117{
1118 int averror = 0;
1119 int syserror = 0;
1120 bool success = true;
1121
1122 const bool partial_multiformat_recode =
1123 cache_entry->m_seek_to_no != 0 &&
1125
1126 if (partial_multiformat_recode)
1127 {
1128 // HLS/frame-set repair run: keep existing segments/frames and start
1129 // directly at the requested item. Existing later items may be
1130 // overwritten by the encoder; missing earlier gaps intentionally stay
1131 // missing.
1132 cache_entry->m_cache_info.m_result = RESULTCODE::NONE;
1133 cache_entry->m_cache_info.m_error = false;
1134 cache_entry->m_cache_info.m_errno = 0;
1135 cache_entry->m_cache_info.m_averror = 0;
1136 }
1137 else
1138 {
1139 // Full transcode run: remove any older remains.
1140 cache_entry->clear();
1141 }
1142
1143 // Must decode the file, otherwise simply use cache
1144 cache_entry->m_is_decoding = true;
1145
1146 try
1147 {
1148 bool unlocked = false;
1149
1150 Logging::info(cache_entry->filename(), "Transcoding to %1.", params.current_format(cache_entry->virtualfile())->desttype().c_str());
1151
1152 if (!cache_entry->openio())
1153 {
1154 throw (static_cast<int>(errno));
1155 }
1156
1157 averror = transcoder.open_input_file(cache_entry->virtualfile());
1158 if (averror < 0)
1159 {
1160 throw (static_cast<int>(errno));
1161 }
1162
1163 if (!cache_entry->m_cache_info.m_duration)
1164 {
1165 cache_entry->m_cache_info.m_duration = transcoder.duration();
1166 }
1167
1168 if (!cache_entry->m_cache_info.m_predicted_filesize)
1169 {
1170 cache_entry->m_cache_info.m_predicted_filesize = transcoder.predicted_filesize();
1171 }
1172
1173 if (!cache_entry->m_cache_info.m_video_frame_count)
1174 {
1175 cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count();
1176 }
1177
1178 if (!cache_entry->m_cache_info.m_segment_count)
1179 {
1180 cache_entry->m_cache_info.m_segment_count = transcoder.segment_count();
1181 }
1182
1183 if (cache != nullptr && !cache->maintenance(transcoder.predicted_filesize()))
1184 {
1185 throw (static_cast<int>(errno));
1186 }
1187
1188 if (transcoder.is_hls())
1189 {
1190 const uint32_t segment_no = cache_entry->m_seek_to_no;
1191 if (segment_no)
1192 {
1193 // Initial direct HLS access: stack the requested segment before
1194 // opening the output. open_output() consumes this immediately
1195 // and seeks the input before segment 1 is opened/written.
1196 cache_entry->m_seek_to_no = 0;
1197
1198 averror = transcoder.stack_seek_segment(segment_no);
1199 if (averror < 0)
1200 {
1201 throw (static_cast<int>(errno));
1202 }
1203 }
1204 }
1205
1206 averror = transcoder.open_output_file(cache_entry->m_buffer.get());
1207 if (averror < 0)
1208 {
1209 throw (static_cast<int>(errno));
1210 }
1211
1212 std::memcpy(&cache_entry->m_id3v1, transcoder.id3v1tag(), sizeof(ID3v1));
1213
1214 thread_data->m_initialised = true;
1215
1216 unlocked = false;
1217 if ((!params.m_prebuffer_size && !params.m_prebuffer_time) || transcoder.is_frameset())
1218 {
1219 // Unlock frame set from beginning
1220 unlocked = true;
1221 thread_data->m_thread_running_lock_guard = true;
1222 thread_data->m_thread_running_cond.notify_all(); // signal that we are running
1223 }
1224 else
1225 {
1227 {
1228 Logging::debug(cache_entry->virtname(), "Pre-buffering up to %1.", format_time(params.m_prebuffer_time).c_str());
1229 }
1231 {
1232 Logging::debug(cache_entry->virtname(), "Pre-buffering up to %1.", format_size(params.m_prebuffer_size).c_str());
1233 }
1234 }
1235
1236 cache_entry->m_suspend_timeout = false;
1237
1238 while (!cache_entry->is_finished() && !(*timeout = cache_entry->decode_timeout()) && !thread_exit)
1239 {
1241
1242 if (cache_entry->ref_count() > 1)
1243 {
1244 // Set last access time
1245 cache_entry->update_access(false);
1246 }
1247
1248 if (transcoder.is_frameset())
1249 {
1250 uint32_t frame_no = cache_entry->m_seek_to_no;
1251 if (frame_no)
1252 {
1253 cache_entry->m_seek_to_no = 0;
1254
1255 averror = transcoder.stack_seek_frame(frame_no);
1256 if (averror < 0)
1257 {
1258 throw (static_cast<int>(errno));
1259 }
1260 }
1261 }
1262 else if (transcoder.is_hls())
1263 {
1264 uint32_t segment_no = cache_entry->m_seek_to_no;
1265 if (segment_no)
1266 {
1267 cache_entry->m_seek_to_no = 0;
1268
1269 averror = transcoder.stack_seek_segment(segment_no);
1270 if (averror < 0)
1271 {
1272 throw (static_cast<int>(errno));
1273 }
1274 }
1275 }
1276
1277 averror = transcoder.process_single_fr(&status);
1278
1279 if (status == DECODER_STATUS::DEC_ERROR)
1280 {
1281 errno = EIO;
1282 throw (static_cast<int>(errno));
1283 }
1284 else if (status == DECODER_STATUS::DEC_EOF)
1285 {
1286 cache_entry->m_suspend_timeout = true; // Suspend read_frame time out until transcoder is reopened.
1287
1288 averror = transcode_finish(cache_entry, transcoder);
1289
1290 if (averror < 0)
1291 {
1292 errno = EIO;
1293 throw (static_cast<int>(errno));
1294 }
1295 }
1296
1297 if (!unlocked && cache_entry->m_buffer->buffer_watermark() > params.m_prebuffer_size && transcoder.pts() > static_cast<int64_t>(params.m_prebuffer_time) * AV_TIME_BASE)
1298 {
1299 unlocked = true;
1300 Logging::info(cache_entry->virtname(), "Pre-buffer limit reached.");
1301
1302 thread_data->m_thread_running_lock_guard = true;
1303 thread_data->m_thread_running_cond.notify_all(); // signal that we are running
1304 }
1305
1306 if (cache_entry->ref_count() <= 1 && cache_entry->suspend_timeout())
1307 {
1308 if (!unlocked && (params.m_prebuffer_size || params.m_prebuffer_time))
1309 {
1310 unlocked = true;
1311 thread_data->m_thread_running_lock_guard = true;
1312 thread_data->m_thread_running_cond.notify_all(); // signal that we are running
1313 }
1314
1315 Logging::info(cache_entry->virtname(), "Timeout! Transcoding suspended after %1 seconds inactivity.", params.m_max_inactive_suspend);
1316
1317 while (cache_entry->suspend_timeout() && !(*timeout = cache_entry->decode_timeout()) && !thread_exit)
1318 {
1320 }
1321
1322 if (*timeout)
1323 {
1324 break;
1325 }
1326
1327 Logging::info(cache_entry->virtname(), "Transcoding resumed.");
1328 }
1329 }
1330
1331 if (!unlocked && (params.m_prebuffer_size || params.m_prebuffer_time))
1332 {
1333 Logging::debug(cache_entry->virtname(), "File transcode complete, releasing buffer early: Size %1.", cache_entry->m_buffer->buffer_watermark());
1334 thread_data->m_thread_running_lock_guard = true;
1335 thread_data->m_thread_running_cond.notify_all(); // signal that we are running
1336 }
1337 }
1338 catch (int _syserror)
1339 {
1340 success = false;
1341 syserror = _syserror;
1342
1343 if (!syserror && averror > -512)
1344 {
1345 // If no system error reported explicitly, and averror is a POSIX error
1346 // (we simply assume that if averror < 512, I think averrors are all higher values)
1347 syserror = AVUNERROR(averror);
1348 }
1349
1350 cache_entry->m_cache_info.m_error = true;
1351 if (!syserror)
1352 {
1353 // If system error is still zero, set to EIO to return at least anything else than success.
1354 syserror = EIO;
1355 }
1356
1357 thread_data->m_thread_running_lock_guard = true;
1358 thread_data->m_thread_running_cond.notify_all(); // unlock main thread
1359 }
1360
1361 cache_entry->m_suspend_timeout = false; // Should end that suspension; otherwise, read may hang.
1362
1363 cache_entry->m_cache_info.m_errno = syserror; // Preserve errno
1364 cache_entry->m_cache_info.m_averror = averror; // Preserve averror
1365
1366 transcoder.closeio();
1367
1368 return success;
1369}
1370
1392static void log_transcoding_result(Cache_Entry* cache_entry,
1393 const FFmpeg_Transcoder& transcoder,
1394 bool timeout,
1395 bool success,
1396 const std::chrono::steady_clock::time_point& start_time)
1397{
1398 if (cache_entry == nullptr)
1399 {
1400 return;
1401 }
1402
1403 if (timeout)
1404 {
1405 Logging::warning(cache_entry->virtname(),
1406 "Timeout! Transcoding aborted after %1 seconds inactivity.",
1408 return;
1409 }
1410
1411 if (thread_exit)
1412 {
1413 Logging::info(cache_entry->virtname(), "Thread exit! Transcoding aborted.");
1414 return;
1415 }
1416
1417 if (success)
1418 {
1419 const auto end_time = std::chrono::steady_clock::now();
1420 const auto elapsed_ms = std::chrono::duration<double, std::milli>(end_time - start_time).count();
1421 char elapsed_ms_text[32];
1422
1423 std::snprintf(elapsed_ms_text, sizeof(elapsed_ms_text), "%.1f", elapsed_ms);
1424
1425 Logging::info(cache_entry->virtname(),
1426 "Transcoding completed successfully after %1 ms.",
1427 elapsed_ms_text);
1428 return;
1429 }
1430
1431 Logging::error(cache_entry->virtname(), "Transcoding exited with error.");
1432
1433 if (cache_entry->m_cache_info.m_errno)
1434 {
1435 Logging::error(cache_entry->virtname(),
1436 "System error: (%1) %2",
1437 cache_entry->m_cache_info.m_errno,
1438 strerror(cache_entry->m_cache_info.m_errno));
1439 }
1440
1441 if (cache_entry->m_cache_info.m_averror)
1442 {
1443 Logging::error(cache_entry->virtname(),
1444 "FFMpeg error: (%1) %2",
1445 cache_entry->m_cache_info.m_averror,
1446 ffmpeg_geterror(cache_entry->m_cache_info.m_averror).c_str());
1447 }
1448}
1449
1455static int transcoder_thread(std::shared_ptr<THREAD_DATA> thread_data)
1456{
1457 Cache_Entry * cache_entry = thread_data->m_cache_entry;
1458 FFmpeg_Transcoder transcoder;
1459 bool timeout = false;
1460 bool success = true;
1461 const auto start_time = std::chrono::steady_clock::now();
1462
1463 std::unique_lock<std::recursive_mutex> lock_active_mutex(cache_entry->m_active_mutex);
1464 std::unique_lock<std::recursive_mutex> lock_restart_mutex(cache_entry->m_restart_mutex);
1465
1466 uint32_t seek_frame = 0;
1467
1468 do
1469 {
1470 if (seek_frame)
1471 {
1472 Logging::error(transcoder.virtname(), "Transcoder completed with last seek frame to %1. Transcoder is being restarted.", seek_frame);
1473 }
1474
1475 success = transcode(thread_data, cache_entry, transcoder, &timeout);
1476
1477 seek_frame = cache_entry->m_seek_to_no != 0 ? cache_entry->m_seek_to_no.load() : transcoder.last_seek_frame_no();
1478
1479 if (transcoder.is_multiformat() && transcoder.have_seeked())
1480 {
1481 // HLS/frame-set caches are intentionally allowed to remain
1482 // incomplete after a seek. Missing segments/frames are repaired
1483 // on demand; restarting from segment/frame 1 here would recreate
1484 // the old loop: transcode, invalidate/restart, seek, repeat.
1485 seek_frame = 0;
1486 }
1487 }
1488 while (success && !thread_exit && cache != nullptr && seek_frame);
1489
1490cache_entry->m_is_decoding = false;
1491
1492 if (timeout || thread_exit || transcoder.have_seeked())
1493 {
1494 if (!transcoder.have_seeked())
1495 {
1496 cache_entry->m_cache_info.m_error = true;
1497 cache_entry->m_cache_info.m_errno = EIO; // Report I/O error
1498 }
1499 else
1500 {
1501 // Must restart from scratch, but this is not an error.
1502 cache_entry->m_cache_info.m_error = false;
1503 cache_entry->m_cache_info.m_errno = 0;
1504 cache_entry->m_cache_info.m_averror = 0;
1505 }
1506 }
1507 else
1508 {
1509 cache_entry->m_cache_info.m_error = !success;
1510
1511 if (success)
1512 {
1513 cache_entry->m_cache_info.m_errno = 0;
1514 cache_entry->m_cache_info.m_averror = 0;
1515 }
1516 }
1517
1518 log_transcoding_result(cache_entry, transcoder, timeout, success, start_time);
1519
1520 int _errno = cache_entry->m_cache_info.m_errno;
1521
1522 if (cache != nullptr)
1523 {
1524 cache->closeio(&cache_entry, timeout ? CACHE_CLOSE_DELETE : CACHE_CLOSE_NOOPT);
1525 }
1526
1527 thread_data.reset();
1528
1529 errno = _errno;
1530
1531 return errno;
1532}
Buffer class.
#define CACHE_FLAG_RO
Mark cache file read-only.
Definition buffer.h:49
#define CACHE_CLOSE_DELETE
Delete cache entry, will unlink cached file! Implies CACHE_CLOSE_FREE.
Definition buffer.h:47
#define CACHE_CLOSE_NOOPT
Dummy, do nothing special.
Definition buffer.h:45
Data cache management.
@ FINISHED_INCOMPLETE
Transcode finished, but incomplete.
@ NONE
No result code available.
@ FINISHED_SUCCESS
Transcode finished successfully.
Cache entry
The Cache_Entry class.
Definition cache_entry.h:49
std::recursive_mutex m_restart_mutex
Mutex while thread is restarted.
size_t size() const
Return size of output file, as computed by encoder.
CACHE_INFO m_cache_info
Info about cached object.
bool update_access(bool update_database=false)
Update last access time.
std::atomic_bool m_suspend_timeout
true to temporarly disable read_frame timeout
bool is_finished() const
Get if cache has been finished.
bool suspend_timeout() const
Check for decode suspend timeout.
int ref_count() const
Get the current reference counter.
void lock()
Lock the access mutex.
std::atomic_bool m_is_decoding
true while file is decoding
const char * virtname() const
Return virtual filename. Same as destination filename, but with virtual (mount) path....
void clear(bool fetch_file_time=true)
Clear the cache entry.
LPVIRTUALFILE virtualfile()
Get the underlying VIRTUALFILE object.
bool openio(bool create_cache=true)
Open the cache file.
void update_read_count()
Update read counter.
const char * filename() const
Return source filename.
std::recursive_mutex m_active_mutex
Mutex while thread is active.
ID3v1 m_id3v1
ID3v1 structure which is used to send to clients.
unsigned int read_count() const
Get read counter.
bool flush()
Flush current memory cache to disk.
bool decode_timeout() const
Check for decode timeout.
void unlock()
Unlock the access mutex.
bool is_finished_incomplete() const
Get if cache has been finished, but not completely filled.
bool is_finished_success() const
Get if cache has been finished and filled successfully.
bool outdated() const
Check if cache entry needs to be recoded.
std::atomic_uint32_t m_seek_to_no
If not 0, seeks to specified frame.
std::unique_ptr< Buffer > m_buffer
Buffer object.
The FFmpeg_Transcoder class.
static bool video_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int width, int height, bool interleaved, const AVRational &framerate)
Predict video file size. This may (better will surely) be inaccurate.
int stack_seek_frame(uint32_t frame_no)
Seek to a specific frame. Does not actually perform the seek, this is done asynchronously by the tran...
virtual const char * filename() const override
Return source filename.
int process_single_fr(DECODER_STATUS *status)
static bool audio_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int channels, int sample_rate, AVSampleFormat sample_format)
Predict audio file size. This may (better will surely) be inaccurate.
bool have_seeked() const
Check if we made a seek operation.
void closeio()
Close transcoder, free all ressources.
uint32_t video_frame_count() const
Get the number of video frames in file.
bool is_hls() const
Check for HLS format.
size_t predicted_filesize() const
Try to predict the recoded file size. This may (better will surely) be inaccurate.
static bool total_overhead(size_t *filesize, FILETYPE filetype)
Predict overhead in file size. This may (better will surely) be inaccurate.
const ID3v1 * id3v1tag() const
Assemble an ID3v1 file tag.
uint32_t segment_count() const
Get the number of HLS segments of file.
bool is_multiformat() const
Check for an export frame format.
virtual const char * virtname() const override
Return virtual filename. Same as destination filename, but with virtual (mount) path....
uint32_t last_seek_frame_no() const
Current seek frame if available.
int open_output_file(Buffer *buffer)
Open output file. Data will actually be written to buffer and copied by FUSE when accessed.
int64_t duration() const
Get the file duration.
bool is_frameset() const
Check for an export frame format.
int64_t pts() const
Get PTS (presentation time stamp) of decoded audio/video so far.
int open_input_file(LPVIRTUALFILE virtualfile, std::shared_ptr< FileIO > fio=nullptr)
int stack_seek_segment(uint32_t segment_no)
Seek to a specific HLS segment. Does not actually perform the seek, this is done asynchronously by th...
The FFmpegfs_Format class.
const std::string & desttype() const
Get destination type.
FILETYPE filetype() const
Get selected filetype.
bool is_multiformat() const
Check if this is some sort of multi file format (any of the following: is_frameset() or is_hls()).
AVCodecID video_codec() const
Get video codec_id.
AVCodecID audio_codec() const
Get audio codec_id.
static void warning(const T filename, const std::string &format_string, Args &&...args)
Write warning level log entry.
Definition logging.h:220
static void debug(const T filename, const std::string &format_string, Args &&...args)
Write debug level log entry.
Definition logging.h:182
static void trace(const T filename, const std::string &format_string, Args &&...args)
Write trace level log entry.
Definition logging.h:163
static void info(const T filename, const std::string &format_string, Args &&...args)
Write info level log entry.
Definition logging.h:201
static void error(const T filename, const std::string &format_string, Args &&...args)
Write error level log entry.
Definition logging.h:239
FFmpeg transcoder.
DECODER_STATUS
Decoder status codes. Can be error, success or end of file.
@ DEC_ERROR
Decoder error, see return code.
@ DEC_EOF
Read to end of file.
@ DEC_SUCCESS
Frame decoded successfully.
bool check_ignore(size_t size, size_t offset)
For use with win_smb_fix=1: Check if this an illegal access offset by Windows.
const char * get_codec_name(AVCodecID codec_id, bool long_name)
Safe way to get the codec name. Function never fails, will return "unknown" on error.
std::string get_filetype_text(FILETYPE filetype)
Convert FILETYPE enum to human readable text.
std::string format_time(time_t value)
Format a time in format "w d m s".
std::string format_result_size_ex(size_t size_resulting, size_t size_predicted)
Format size of transcoded file including difference between predicted and resulting size.
const std::string & append_sep(std::string *path)
Add / to the path if required.
const std::string & expand_path(std::string *tgt, const std::string &src)
Expand path, e.g., expand ~/ to home directory.
void mssleep(int milliseconds)
Sleep for specified time.
std::string format_size(uint64_t value)
Format size.
void stat_set_size(struct stat *st, size_t size)
Properly fill in all size related members in stat struct.
std::string format_size_ex(uint64_t value)
Format size.
std::string ffmpeg_geterror(int errnum)
Get FFmpeg error string for errnum. Internally calls av_strerror().
#define BITRATE
For FFmpeg bit rate is an int.
FFMPEGFS_PARAMS params
FFmpegfs command line parameters.
Definition ffmpegfs.cc:81
Main include for FFmpegfs project.
std::unique_ptr< thread_pool > tp
Thread pool object.
Definition fuseops.cc:102
#define ID3V1_TAG_LENGTH
Fixed 128 bytes.
Definition id3v1tag.h:58
Provide various log facilities to stderr, disk or syslog.
uint32_t m_video_frame_count
Number of frames in video or 0 if not a video.
Definition cache.h:84
RESULTCODE m_result
Result code:
Definition cache.h:86
int64_t m_duration
File duration, in AV_TIME_BASE fractional seconds.
Definition cache.h:81
uint32_t m_segment_count
Number of segments for HLS.
Definition cache.h:85
size_t m_encoded_filesize
Actual file size after encode.
Definition cache.h:83
bool m_error
true if encode failed
Definition cache.h:87
size_t m_predicted_filesize
Predicted file size.
Definition cache.h:82
int m_averror
FFmpeg error code if encode failed.
Definition cache.h:89
int m_errno
errno if encode failed
Definition cache.h:88
time_t m_max_inactive_suspend
Time (seconds) that must elapse without access until transcoding is suspended.
Definition ffmpegfs.h:239
const FFmpegfs_Format * current_format(LPCVIRTUALFILE virtualfile) const
Get FFmpegfs_Format for a virtual file.
Definition ffmpegfs.cc:242
int64_t m_min_seek_time_diff
Minimum time diff from current to next requested segment to perform a seek, in AV_TIME_BASE fractiona...
Definition ffmpegfs.h:214
size_t m_prebuffer_size
Number of bytes that will be decoded before the output can be accessed.
Definition ffmpegfs.h:242
time_t m_prebuffer_time
Playing time that will be decoded before the output can be accessed.
Definition ffmpegfs.h:241
int64_t m_segment_duration
Duration of one HLS segment file, in AV_TIME_BASE fractional seconds.
Definition ffmpegfs.h:213
std::string m_cachepath
Disk cache path, defaults to $XDG_CACHE_HOME.
Definition ffmpegfs.h:245
int m_disable_cache
Disable cache.
Definition ffmpegfs.h:246
time_t m_max_inactive_abort
Time (seconds) that must elapse without access until transcoding is aborted.
Definition ffmpegfs.h:240
int m_win_smb_fix
Experimental Windows fix for access to EOF at file open.
Definition ffmpegfs.h:258
ID3 version 1 tag
Definition id3v1tag.h:42
THREAD_DATA struct to pass data from parent to child thread.
Definition transcode.cc:57
std::mutex m_thread_running_mutex
Mutex when thread is running.
Definition transcode.cc:58
bool m_initialised
True when this object is completely initialised.
Definition transcode.cc:61
std::atomic_bool m_thread_running_lock_guard
Lock guard to avoid spurious or missed unlocks.
Definition transcode.cc:60
Cache_Entry * m_cache_entry
Cache entry object. Will not be freed by child thread.
Definition transcode.cc:62
std::condition_variable m_thread_running_cond
Condition when thread is running.
Definition transcode.cc:59
Virtual file definition.
Definition fileio.h:123
size_t m_predicted_size
Use this as the size instead of computing it over and over.
Definition fileio.h:157
struct stat m_st
stat structure with size etc.
Definition fileio.h:153
uint32_t get_segment_count() const
Number of HLS segments in set.
Definition fileio.cc:45
uint32_t m_video_frame_count
Number of frames in video or 0 if not a video.
Definition fileio.h:158
int64_t m_duration
Track/chapter duration, in AV_TIME_BASE fractional seconds.
Definition fileio.h:156
Thread pool class implementation.
static bool transcode_until(Cache_Entry *cache_entry, size_t offset, size_t len, uint32_t segment_no)
Transcode the buffer until the buffer has enough or until an error occurs. The buffer needs at least ...
Definition transcode.cc:87
static bool transcode(std::shared_ptr< THREAD_DATA > thread_data, Cache_Entry *cache_entry, FFmpeg_Transcoder &transcoder, bool *timeout)
Actually transcode file.
const int TOTAL_RETRIES
Number of retries.
Definition transcode.cc:52
bool transcoder_predict_filesize(LPVIRTUALFILE virtualfile, Cache_Entry *cache_entry)
Predict file size.
Definition transcode.cc:469
static std::atomic_bool thread_exit
Used for shutdown: if true, forcibly exit all threads.
Definition transcode.cc:66
void transcoder_cache_path(std::string *path)
Get transcoder cache path.
Definition transcode.cc:336
Cache_Entry * transcoder_new(LPVIRTUALFILE virtualfile, bool begin_transcode)
Allocate and initialise the transcoder.
Definition transcode.cc:590
bool transcoder_set_filesize(LPVIRTUALFILE virtualfile, int64_t duration, BITRATE audio_bit_rate, int channels, int sample_rate, AVSampleFormat sample_format, BITRATE video_bit_rate, int width, int height, bool interleaved, const AVRational &framerate)
Set the file size.
Definition transcode.cc:429
static std::unique_ptr< Cache > cache
Global cache manager object.
Definition transcode.cc:65
static bool invalidate_stale_cache_file(Cache_Entry *cache_entry, uint32_t segment_no, uint32_t item_no, const char *item_name)
Invalidate a stale physical cache file and make the entry eligible for recoding.
Definition transcode.cc:246
static void log_transcoding_result(Cache_Entry *cache_entry, const FFmpeg_Transcoder &transcoder, bool timeout, bool success, const std::chrono::steady_clock::time_point &start_time)
Log the final result of a transcoder worker run.
void transcoder_free()
Free transcoder.
Definition transcode.cc:392
const int GRANULARITY
Image frame conversion: ms between checks if a picture frame is available.
Definition transcode.cc:50
size_t transcoder_buffer_tell(Cache_Entry *cache_entry, uint32_t segment_no)
Return the current file position in the file.
bool transcoder_read(Cache_Entry *cache_entry, char *buff, size_t offset, size_t len, int *bytes_read, uint32_t segment_no)
Read some bytes from the internal buffer and into the given buffer.
Definition transcode.cc:677
static int start_transcoder_thread(Cache_Entry *cache_entry)
Start the transcoder thread for an existing cache entry.
Definition transcode.cc:504
static int transcode_finish(Cache_Entry *cache_entry, FFmpeg_Transcoder &transcoder)
Close the input file and free everything but the initial buffer.
Definition transcode.cc:161
void transcoder_exit()
Exit transcoding.
size_t transcoder_get_size(Cache_Entry *cache_entry)
Return size of output file, as computed by encoder.
bool transcoder_read_frame(Cache_Entry *cache_entry, char *buff, size_t offset, size_t len, uint32_t frame_no, int *bytes_read, LPVIRTUALFILE virtualfile)
Read one image frame from the internal buffer and into the given buffer.
Definition transcode.cc:892
bool transcoder_init()
Initialise transcoder, create cache.
Definition transcode.cc:370
static bool cached_item_available(Cache_Entry *cache_entry, size_t offset, size_t len, uint32_t segment_no)
Check whether the requested cache data is available and physically readable.
Definition transcode.cc:306
size_t transcoder_buffer_watermark(Cache_Entry *cache_entry, uint32_t segment_no)
Return the current watermark of the file while transcoding.
bool transcoder_cache_clear()
Clear transcoder cache.
static int transcoder_thread(std::shared_ptr< THREAD_DATA > thread_data)
Transcoding thread.
bool transcoder_cached_filesize(LPVIRTUALFILE virtualfile, struct stat *stbuf)
Simply get encoded file size (do not create the whole encoder/decoder objects)
Definition transcode.cc:402
static void reopen_finished_incomplete_cache(Cache_Entry *cache_entry, uint32_t item_no, const char *item_name)
Re-open an incomplete multi-format cache for an on-demand repair run.
Definition transcode.cc:214
bool transcoder_cache_maintenance()
Run cache maintenance.
const int FRAME_TIMEOUT
Image frame conversion: timout seconds to wait if a picture frame is available.
Definition transcode.cc:51
void transcoder_delete(Cache_Entry *cache_entry)
Free the cache entry structure.
File transcoder interface (for use with by FUSE)