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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | namespace QFFmpeg { |
13 | |
14 | static Q_LOGGING_CATEGORY(qLcEncodingFormatContext, "qt.multimedia.ffmpeg.encodingformatcontext" ); |
15 | |
16 | namespace { |
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. |
19 | constexpr size_t DefaultBufferSize = 4096; |
20 | } // namespace |
21 | |
22 | EncodingFormatContext::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 | |
29 | EncodingFormatContext::~EncodingFormatContext() |
30 | { |
31 | closeAVIO(); |
32 | |
33 | avformat_free_context(s: m_avFormatContext); |
34 | } |
35 | |
36 | void 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 | |
60 | void 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 | |
80 | void 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 | |
93 | void 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 | |
116 | QT_END_NAMESPACE |
117 | |