| e8555c89 Andy McCall |
1 |
|
| 2 |
|
| 3 |
#include "AviWriter.hh" |
| 4 |
|
| 5 |
#include "FileOperations.hh" |
| 6 |
#include "MSXException.hh" |
| 7 |
#include "Version.hh" |
| 8 |
|
| 9 |
#include "cstdiop.hh" |
| 10 |
#include "endian.hh" |
| 11 |
#include "narrow.hh" |
| 12 |
#include "ranges.hh" |
| 13 |
#include "small_buffer.hh" |
| 14 |
#include "stl.hh" |
| 15 |
#include "zstring_view.hh" |
| 16 |
|
| 17 |
#include <array> |
| 18 |
#include <cassert> |
| 19 |
#include <cstring> |
| 20 |
#include <ctime> |
| 21 |
#include <limits> |
| 22 |
|
| 23 |
namespace openmsx { |
| 24 |
|
| 25 |
static constexpr unsigned AVI_HEADER_SIZE = 500; |
| 26 |
|
| 27 |
AviWriter::AviWriter(const std::string& filename_, unsigned width_, |
| 28 |
unsigned height_, unsigned channels_, unsigned freq_) |
| 29 |
: file(filename_, "wb") |
| 30 |
, filename(filename_) |
| 31 |
, codec(width_, height_) |
| 32 |
, width(width_) |
| 33 |
, height(height_) |
| 34 |
, channels(channels_) |
| 35 |
, audioRate(freq_) |
| 36 |
{ |
| 37 |
std::array<uint8_t, AVI_HEADER_SIZE> dummy = {}; |
| 38 |
file.write(dummy); |
| 39 |
|
| 40 |
index.resize(2); |
| 41 |
} |
| 42 |
|
| 43 |
AviWriter::~AviWriter() |
| 44 |
{ |
| 45 |
if (written == 0) { |
| 46 |
|
| 47 |
file.close(); |
| 48 |
FileOperations::unlink(filename); |
| 49 |
return; |
| 50 |
} |
| 51 |
assert(fps != 0.0f); |
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
std::array<uint8_t, AVI_HEADER_SIZE> avi_header = {}; |
| 56 |
unsigned header_pos = 0; |
| 57 |
|
| 58 |
auto AVIOUT4 = [&](std::string_view s) { |
| 59 |
assert(s.size() == 4); |
| 60 |
copy_to_range(s, subspan(avi_header, header_pos)); |
| 61 |
header_pos += 4; |
| 62 |
}; |
| 63 |
auto AVIOUTw = [&](uint16_t w) { |
| 64 |
Endian::write_UA_L16(&avi_header[header_pos], w); |
| 65 |
header_pos += sizeof(w); |
| 66 |
}; |
| 67 |
auto AVIOUTd = [&](uint32_t d) { |
| 68 |
Endian::write_UA_L32(&avi_header[header_pos], d); |
| 69 |
header_pos += sizeof(d); |
| 70 |
}; |
| 71 |
auto AVIOUTs = [&](zstring_view s) { |
| 72 |
auto len1 = s.size() + 1; |
| 73 |
copy_to_range(std::span{s.data(), len1}, subspan(avi_header, header_pos)); |
| 74 |
header_pos += narrow<unsigned>((len1 + 1) & ~1); |
| 75 |
}; |
| 76 |
|
| 77 |
bool hasAudio = audioRate != 0; |
| 78 |
|
| 79 |
|
| 80 |
AVIOUT4("RIFF"); |
| 81 |
AVIOUTd(AVI_HEADER_SIZE + written - 8 + unsigned(index.size() * sizeof(Endian::L32))); |
| 82 |
AVIOUT4("AVI "); |
| 83 |
AVIOUT4("LIST"); |
| 84 |
auto main_list = header_pos; |
| 85 |
AVIOUTd(0); |
| 86 |
AVIOUT4("hdrl"); |
| 87 |
|
| 88 |
AVIOUT4("avih"); |
| 89 |
AVIOUTd(56); |
| 90 |
AVIOUTd(uint32_t(1000000 / fps)); |
| 91 |
AVIOUTd(0); |
| 92 |
AVIOUTd(0); |
| 93 |
AVIOUTd(0x110); |
| 94 |
AVIOUTd(frames); |
| 95 |
AVIOUTd(0); |
| 96 |
AVIOUTd(hasAudio? 2 : 1); |
| 97 |
AVIOUTd(0); |
| 98 |
AVIOUTd(width); |
| 99 |
AVIOUTd(height); |
| 100 |
AVIOUTd(0); |
| 101 |
AVIOUTd(0); |
| 102 |
AVIOUTd(0); |
| 103 |
AVIOUTd(0); |
| 104 |
|
| 105 |
|
| 106 |
AVIOUT4("LIST"); |
| 107 |
AVIOUTd(4 + 8 + 56 + 8 + 40); |
| 108 |
AVIOUT4("strl"); |
| 109 |
|
| 110 |
AVIOUT4("strh"); |
| 111 |
AVIOUTd(56); |
| 112 |
AVIOUT4("vids"); |
| 113 |
AVIOUT4(ZMBVEncoder::CODEC_4CC); |
| 114 |
AVIOUTd(0); |
| 115 |
AVIOUTd(0); |
| 116 |
AVIOUTd(0); |
| 117 |
AVIOUTd(1000000); |
| 118 |
AVIOUTd(uint32_t(1000000 * fps)); |
| 119 |
AVIOUTd(0); |
| 120 |
AVIOUTd(frames); |
| 121 |
AVIOUTd(0); |
| 122 |
AVIOUTd(uint32_t(~0)); |
| 123 |
AVIOUTd(0); |
| 124 |
AVIOUTd(0); |
| 125 |
AVIOUTd(0); |
| 126 |
|
| 127 |
AVIOUT4("strf"); |
| 128 |
AVIOUTd(40); |
| 129 |
AVIOUTd(40); |
| 130 |
AVIOUTd(width); |
| 131 |
AVIOUTd(height); |
| 132 |
AVIOUTd(0); |
| 133 |
AVIOUT4(ZMBVEncoder::CODEC_4CC); |
| 134 |
AVIOUTd(width * height * 4); |
| 135 |
AVIOUTd(0); |
| 136 |
AVIOUTd(0); |
| 137 |
AVIOUTd(0); |
| 138 |
AVIOUTd(0); |
| 139 |
|
| 140 |
if (hasAudio) { |
| 141 |
|
| 142 |
uint16_t bitsPerSample = 16; |
| 143 |
unsigned bytesPerSample = bitsPerSample / 8; |
| 144 |
unsigned bytesPerFragment = bytesPerSample * channels; |
| 145 |
unsigned bytesPerSecond = audioRate * bytesPerFragment; |
| 146 |
unsigned fragments = audioWritten / channels; |
| 147 |
|
| 148 |
|
| 149 |
AVIOUT4("LIST"); |
| 150 |
AVIOUTd(4 + 8 + 56 + 8 + 16); |
| 151 |
AVIOUT4("strl"); |
| 152 |
|
| 153 |
AVIOUT4("strh"); |
| 154 |
AVIOUTd(56); |
| 155 |
AVIOUT4("auds"); |
| 156 |
AVIOUTd(0); |
| 157 |
AVIOUTd(0); |
| 158 |
AVIOUTd(0); |
| 159 |
AVIOUTd(0); |
| 160 |
|
| 161 |
AVIOUTd(bytesPerFragment); |
| 162 |
AVIOUTd(bytesPerSecond); |
| 163 |
AVIOUTd(0); |
| 164 |
AVIOUTd(fragments); |
| 165 |
AVIOUTd(0); |
| 166 |
AVIOUTd(unsigned(~0)); |
| 167 |
AVIOUTd(bytesPerFragment); |
| 168 |
AVIOUTd(0); |
| 169 |
AVIOUTd(0); |
| 170 |
|
| 171 |
AVIOUT4("strf"); |
| 172 |
AVIOUTd(16); |
| 173 |
AVIOUTw(1); |
| 174 |
AVIOUTw(narrow<uint16_t>(channels)); |
| 175 |
AVIOUTd(audioRate); |
| 176 |
AVIOUTd(bytesPerSecond); |
| 177 |
AVIOUTw(narrow<uint16_t>(bytesPerFragment)); |
| 178 |
AVIOUTw(bitsPerSample); |
| 179 |
} |
| 180 |
|
| 181 |
auto versionStr = Version::full(); |
| 182 |
|
| 183 |
|
| 184 |
|
| 185 |
|
| 186 |
|
| 187 |
|
| 188 |
|
| 189 |
|
| 190 |
|
| 191 |
|
| 192 |
|
| 193 |
|
| 194 |
constexpr size_t size = (4 + 1 + 2 + 1 + 2 + 1) + 22; |
| 195 |
std::array<char, size> dateStr; |
| 196 |
time_t t = time(nullptr); |
| 197 |
const struct tm* tm = localtime(&t); |
| 198 |
size_t dateLen = snprintf(dateStr.data(), sizeof(dateStr), "%04d-%02d-%02d", 1900 + tm->tm_year, |
| 199 |
tm->tm_mon + 1, tm->tm_mday); |
| 200 |
assert(dateLen < size); |
| 201 |
|
| 202 |
AVIOUT4("LIST"); |
| 203 |
AVIOUTd(narrow<uint32_t>( |
| 204 |
4 |
| 205 |
+ (4 + 4 + ((versionStr.size() + 1 + 1) & ~1)) |
| 206 |
+ (4 + 4 + ((dateLen + 1 + 1) & ~1)) |
| 207 |
)); |
| 208 |
AVIOUT4("INFO"); |
| 209 |
AVIOUT4("ISFT"); |
| 210 |
AVIOUTd(unsigned(versionStr.size()) + 1); |
| 211 |
AVIOUTs(versionStr); |
| 212 |
AVIOUT4("ICRD"); |
| 213 |
AVIOUTd(unsigned(dateLen) + 1); |
| 214 |
AVIOUTs(zstring_view{dateStr.data(), dateLen}); |
| 215 |
|
| 216 |
|
| 217 |
|
| 218 |
|
| 219 |
|
| 220 |
auto nMain = header_pos - main_list - 4; |
| 221 |
auto nJunk = AVI_HEADER_SIZE - 8 - 12 - header_pos; |
| 222 |
assert(nJunk > 0); |
| 223 |
AVIOUT4("JUNK"); |
| 224 |
AVIOUTd(nJunk); |
| 225 |
|
| 226 |
header_pos = main_list; |
| 227 |
AVIOUTd(nMain); |
| 228 |
header_pos = AVI_HEADER_SIZE - 12; |
| 229 |
|
| 230 |
AVIOUT4("LIST"); |
| 231 |
AVIOUTd(written + 4); |
| 232 |
AVIOUT4("movi"); |
| 233 |
|
| 234 |
try { |
| 235 |
|
| 236 |
unsigned idxSize = unsigned(index.size()) * sizeof(Endian::L32); |
| 237 |
index[0] = ('i' << 0) | ('d' << 8) | ('x' << 16) | ('1' << 24); |
| 238 |
index[1] = idxSize - 8; |
| 239 |
file.write(std::span{index}); |
| 240 |
file.seek(0); |
| 241 |
file.write(avi_header); |
| 242 |
} catch (MSXException&) { |
| 243 |
|
| 244 |
} |
| 245 |
} |
| 246 |
|
| 247 |
void AviWriter::addAviChunk(std::span<const char, 4> tag, std::span<const uint8_t> data, unsigned flags) |
| 248 |
{ |
| 249 |
struct { |
| 250 |
std::array<char, 4> t; |
| 251 |
Endian::L32 s; |
| 252 |
} chunk; |
| 253 |
|
| 254 |
auto size32 = narrow<uint32_t>(data.size()); |
| 255 |
|
| 256 |
copy_to_range(tag, chunk.t); |
| 257 |
chunk.s = size32; |
| 258 |
file.write(std::span{&chunk, 1}); |
| 259 |
|
| 260 |
file.write(data); |
| 261 |
unsigned pos = written + 4; |
| 262 |
written += size32 + 8; |
| 263 |
if (size32 & 1) { |
| 264 |
std::array<uint8_t, 1> padding = {0}; |
| 265 |
file.write(padding); |
| 266 |
++written; |
| 267 |
} |
| 268 |
|
| 269 |
size_t idxSize = index.size(); |
| 270 |
index.resize(idxSize + 4); |
| 271 |
memcpy(&index[idxSize], tag.data(), tag.size()); |
| 272 |
index[idxSize + 1] = flags; |
| 273 |
index[idxSize + 2] = pos; |
| 274 |
index[idxSize + 3] = size32; |
| 275 |
} |
| 276 |
|
| 277 |
void AviWriter::addFrame(const FrameSource* video, std::span<const int16_t> audio) |
| 278 |
{ |
| 279 |
bool keyFrame = (frames++ % 300 == 0); |
| 280 |
auto buffer = codec.compressFrame(keyFrame, video); |
| 281 |
addAviChunk(subspan<4>("00dc"), buffer, keyFrame ? 0x10 : 0x0); |
| 282 |
|
| 283 |
if (!audio.empty()) { |
| 284 |
assert((audio.size() % channels) == 0); |
| 285 |
assert(audioRate != 0); |
| 286 |
if constexpr (Endian::BIG) { |
| 287 |
small_buffer<Endian::L16, 4096> buf(audio); |
| 288 |
addAviChunk(subspan<4>("01wb"), as_byte_span(std::span{buf}), 0); |
| 289 |
} else { |
| 290 |
addAviChunk(subspan<4>("01wb"), as_byte_span(audio), 0); |
| 291 |
} |
| 292 |
audioWritten += narrow<uint32_t>(audio.size()); |
| 293 |
} |
| 294 |
} |
| 295 |
|
| 296 |
} |