1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qffmpegencodingformatcontext_p.h"
5#include "qffmpegmediaformatinfo_p.h"
6#include "qffmpegioutils_p.h"
7#include "qfile.h"
8#include "QtCore/qloggingcategory.h"
9
10QT_BEGIN_NAMESPACE
11
12namespace QFFmpeg {
13
14static Q_LOGGING_CATEGORY(qLcEncodingFormatContext, "qt.multimedia.ffmpeg.encodingformatcontext");
15
16namespace {
17// In the example https://ffmpeg.org/doxygen/trunk/avio_read_callback_8c-example.html,
18// BufferSize = 4096 is suggested, however, it might be not optimal. To be investigated.
19constexpr size_t DefaultBufferSize = 4096;
20} // namespace
21
22EncodingFormatContext::EncodingFormatContext(QMediaFormat::FileFormat fileFormat)
23 : m_avFormatContext(avformat_alloc_context())
24{
25 const AVOutputFormat *avFormat = QFFmpegMediaFormatInfo::outputFormatForFileFormat(format: fileFormat);
26 m_avFormatContext->oformat = const_cast<AVOutputFormat *>(avFormat); // constness varies
27}
28
29EncodingFormatContext::~EncodingFormatContext()
30{
31 closeAVIO();
32
33 avformat_free_context(s: m_avFormatContext);
34}
35
36void EncodingFormatContext::openAVIO(const QString &filePath)
37{
38 Q_ASSERT(!isAVIOOpen());
39 Q_ASSERT(!filePath.isEmpty());
40
41 const QByteArray filePathUtf8 = filePath.toUtf8();
42
43 std::unique_ptr<char, decltype(&av_free)> url(
44 reinterpret_cast<char *>(av_malloc(size: filePathUtf8.size() + 1)), &av_free);
45 memcpy(dest: url.get(), src: filePathUtf8.constData(), n: filePathUtf8.size() + 1);
46
47 // Initialize the AVIOContext for accessing the resource indicated by the url
48 auto result = avio_open2(s: &m_avFormatContext->pb, url: url.get(), AVIO_FLAG_WRITE, int_cb: nullptr, options: nullptr);
49
50 qCDebug(qLcEncodingFormatContext)
51 << "opened by file path:" << url.get() << ", result:" << result;
52
53 Q_ASSERT(m_avFormatContext->url == nullptr);
54 if (isAVIOOpen())
55 m_avFormatContext->url = url.release();
56 else
57 openAVIOWithQFile(filePath);
58}
59
60void EncodingFormatContext::openAVIOWithQFile(const QString &filePath)
61{
62 // QTBUG-123082, To be investigated:
63 // - should we use the logic with QFile for all file paths?
64 // - does avio_open2 handle network protocols that QFile doesn't?
65 // - which buffer size should we set to opening with QFile to ensure the best performance?
66
67 auto file = std::make_unique<QFile>(args: filePath);
68
69 if (!file->open(flags: QFile::WriteOnly)) {
70 qCDebug(qLcEncodingFormatContext) << "Cannot open QFile" << filePath;
71 return;
72 }
73
74 openAVIO(device: file.get());
75
76 if (isAVIOOpen())
77 m_outputFile = std::move(file);
78}
79
80void EncodingFormatContext::openAVIO(QIODevice *device)
81{
82 Q_ASSERT(!isAVIOOpen());
83 Q_ASSERT(device);
84
85 if (!device->isWritable())
86 return;
87
88 auto buffer = static_cast<uint8_t *>(av_malloc(size: DefaultBufferSize));
89 m_avFormatContext->pb = avio_alloc_context(buffer, buffer_size: DefaultBufferSize, write_flag: 1, opaque: device, read_packet: nullptr,
90 write_packet: &writeQIODevice, seek: &seekQIODevice);
91}
92
93void EncodingFormatContext::closeAVIO()
94{
95 // Close the AVIOContext and release any file handles
96 if (isAVIOOpen()) {
97 if (m_avFormatContext->url && *m_avFormatContext->url != '\0') {
98 auto closeResult = avio_closep(s: &m_avFormatContext->pb);
99 Q_ASSERT(closeResult == 0);
100 } else {
101 av_free(ptr: std::exchange(obj&: m_avFormatContext->pb->buffer, new_val: nullptr));
102 avio_context_free(s: &m_avFormatContext->pb);
103 }
104
105 // delete url even though it might be delete by avformat_free_context to
106 // ensure consistency in openAVIO/closeAVIO.
107 av_freep(ptr: &m_avFormatContext->url);
108 m_outputFile.reset();
109 } else {
110 Q_ASSERT(!m_outputFile);
111 }
112}
113
114} // namespace QFFmpeg
115
116QT_END_NAMESPACE
117

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegencodingformatcontext.cpp