rays-video 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.doc/ext/rays-video/video.cpp +68 -57
- data/.github/workflows/release-gem.yml +2 -49
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +1 -37
- data/ChangeLog.md +15 -0
- data/Gemfile.lock +26 -0
- data/README.md +24 -2
- data/VERSION +1 -1
- data/ext/rays-video/extconf.rb +2 -1
- data/ext/rays-video/video.cpp +72 -60
- data/include/rays/video.h +19 -15
- data/lib/rays/video.rb +4 -0
- data/rays-video.gemspec +5 -5
- data/src/ios/video.mm +1 -632
- data/src/ios/video_audio_in.mm +1 -251
- data/src/osx/video.mm +283 -57
- data/src/osx/video_audio_in.mm +2 -1
- data/src/sdl/video.cpp +19 -13
- data/src/video.cpp +202 -85
- data/src/video.h +7 -5
- data/src/win32/video.cpp +19 -13
- data/test/test_video.rb +164 -27
- metadata +12 -13
- data/.github/workflows/utils.rb +0 -127
- data/src/ios/video_audio_in.h +0 -22
data/src/osx/video.mm
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
#import <AVFoundation/AVFoundation.h>
|
|
7
7
|
#import <ImageIO/ImageIO.h>
|
|
8
8
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
|
9
|
+
#ifdef IOS
|
|
10
|
+
#import <MobileCoreServices/UTCoreTypes.h>
|
|
11
|
+
#endif
|
|
12
|
+
#include <xot/util.h>
|
|
9
13
|
#include "rays/bitmap.h"
|
|
10
14
|
#include "rays/exception.h"
|
|
11
15
|
#include "video_audio_in.h"
|
|
@@ -15,63 +19,134 @@ namespace Rays
|
|
|
15
19
|
{
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
struct
|
|
22
|
+
struct VideoDecoder::Data
|
|
19
23
|
{
|
|
20
24
|
|
|
25
|
+
String path;
|
|
26
|
+
|
|
21
27
|
virtual ~Data () {}
|
|
22
28
|
|
|
23
|
-
virtual
|
|
29
|
+
virtual void get_bitmap (Bitmap* bitmap, size_t index) = 0;
|
|
24
30
|
|
|
25
31
|
virtual VideoAudioInList get_audio_tracks () const
|
|
26
32
|
{
|
|
27
33
|
return {};
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
virtual coord width () const
|
|
36
|
+
virtual coord width () const = 0;
|
|
31
37
|
|
|
32
|
-
virtual coord height () const
|
|
38
|
+
virtual coord height () const = 0;
|
|
33
39
|
|
|
34
|
-
virtual float fps () const
|
|
40
|
+
virtual float fps () const = 0;
|
|
35
41
|
|
|
36
|
-
virtual size_t size () const
|
|
42
|
+
virtual size_t size () const = 0;
|
|
37
43
|
|
|
38
44
|
virtual operator bool () const = 0;
|
|
39
45
|
|
|
40
|
-
};//
|
|
46
|
+
};// VideoDecoder::Data
|
|
41
47
|
|
|
42
48
|
|
|
43
|
-
static
|
|
44
|
-
|
|
49
|
+
static void
|
|
50
|
+
copy_pixels (Bitmap* bitmap, CMSampleBufferRef sample)
|
|
45
51
|
{
|
|
52
|
+
if (!bitmap)
|
|
53
|
+
argument_error(__FILE__, __LINE__);
|
|
54
|
+
if (!*bitmap)
|
|
55
|
+
argument_error(__FILE__, __LINE__, "bitmap is empty");
|
|
56
|
+
if (!sample)
|
|
57
|
+
argument_error(__FILE__, __LINE__);
|
|
58
|
+
|
|
59
|
+
CVImageBufferRef pixel_buffer = CMSampleBufferGetImageBuffer(sample);
|
|
60
|
+
if (!pixel_buffer)
|
|
61
|
+
rays_error(__FILE__, __LINE__, "sample has no image buffer");
|
|
62
|
+
|
|
63
|
+
int w = (int) CVPixelBufferGetWidth(pixel_buffer);
|
|
64
|
+
int h = (int) CVPixelBufferGetHeight(pixel_buffer);
|
|
65
|
+
if (bitmap->width() != w || bitmap->height() != h)
|
|
66
|
+
{
|
|
67
|
+
rays_error(
|
|
68
|
+
__FILE__, __LINE__,
|
|
69
|
+
"frame size %dx%d does not match the video size %dx%d",
|
|
70
|
+
w, h, bitmap->width(), bitmap->height());
|
|
71
|
+
}
|
|
72
|
+
if (bitmap->color_space().type() != RGBA)
|
|
73
|
+
argument_error(__FILE__, __LINE__, "bitmap must be RGBA");
|
|
74
|
+
|
|
75
|
+
CVPixelBufferLockBaseAddress(pixel_buffer, kCVPixelBufferLock_ReadOnly);
|
|
76
|
+
{
|
|
77
|
+
// the reader hands out BGRA, the bitmap is RGBA
|
|
78
|
+
const uint8_t* src = (const uint8_t*) CVPixelBufferGetBaseAddress(pixel_buffer);
|
|
79
|
+
size_t src_pitch = CVPixelBufferGetBytesPerRow(pixel_buffer);
|
|
80
|
+
for (int y = 0; y < h; ++y)
|
|
81
|
+
{
|
|
82
|
+
const uint8_t* s = src + y * src_pitch;
|
|
83
|
+
uint8_t* d = bitmap->at<uint8_t>(0, y);
|
|
84
|
+
for (int x = 0; x < w; ++x, s += 4, d += 4)
|
|
85
|
+
{
|
|
86
|
+
d[0] = s[2];
|
|
87
|
+
d[1] = s[1];
|
|
88
|
+
d[2] = s[0];
|
|
89
|
+
d[3] = s[3];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
CVPixelBufferUnlockBaseAddress(pixel_buffer, kCVPixelBufferLock_ReadOnly);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static void
|
|
97
|
+
copy_pixels (Bitmap* bitmap, CGImageRef cgimage)
|
|
98
|
+
{
|
|
99
|
+
if (!bitmap)
|
|
100
|
+
argument_error(__FILE__, __LINE__);
|
|
101
|
+
if (!*bitmap)
|
|
102
|
+
argument_error(__FILE__, __LINE__, "bitmap is empty");
|
|
46
103
|
if (!cgimage)
|
|
47
104
|
argument_error(__FILE__, __LINE__);
|
|
48
105
|
|
|
49
106
|
int w = (int) CGImageGetWidth(cgimage);
|
|
50
107
|
int h = (int) CGImageGetHeight(cgimage);
|
|
51
|
-
|
|
108
|
+
if (bitmap->width() != w || bitmap->height() != h)
|
|
109
|
+
{
|
|
110
|
+
rays_error(
|
|
111
|
+
__FILE__, __LINE__,
|
|
112
|
+
"frame size %dx%d does not match the video size %dx%d",
|
|
113
|
+
w, h, bitmap->width(), bitmap->height());
|
|
114
|
+
}
|
|
115
|
+
if (bitmap->color_space().type() != RGBA)
|
|
116
|
+
argument_error(__FILE__, __LINE__, "bitmap must be RGBA");
|
|
52
117
|
|
|
53
118
|
std::shared_ptr<CGColorSpace> colorspace(
|
|
54
119
|
CGColorSpaceCreateDeviceRGB(),
|
|
55
120
|
CGColorSpaceRelease);
|
|
56
121
|
std::shared_ptr<CGContext> context(
|
|
57
122
|
CGBitmapContextCreate(
|
|
58
|
-
|
|
123
|
+
bitmap->pixels(), w, h, 8, bitmap->pitch(), colorspace.get(),
|
|
59
124
|
(CGBitmapInfo) kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big),
|
|
60
125
|
CGContextRelease);
|
|
126
|
+
CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
|
|
61
127
|
CGContextDrawImage(context.get(), CGRectMake(0, 0, w, h), cgimage);
|
|
62
|
-
|
|
63
|
-
return bmp;
|
|
64
128
|
}
|
|
65
129
|
|
|
66
130
|
|
|
67
|
-
|
|
131
|
+
typedef std::shared_ptr<opaqueCMSampleBuffer> CMSampleBufferPtr;
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
struct VideoFileDecoder : VideoDecoder::Data
|
|
68
135
|
{
|
|
69
136
|
|
|
70
|
-
|
|
137
|
+
enum {SKIP_MAX = 10};
|
|
138
|
+
|
|
139
|
+
AVAsset* asset = nil;
|
|
140
|
+
|
|
141
|
+
AVAssetTrack* video_track = nil;
|
|
71
142
|
|
|
72
|
-
|
|
143
|
+
AVAssetReader* reader = nil;
|
|
73
144
|
|
|
74
|
-
|
|
145
|
+
AVAssetReaderOutput* output = nil;
|
|
146
|
+
|
|
147
|
+
ssize_t next_index = -1;
|
|
148
|
+
|
|
149
|
+
VideoFileDecoder (const char* path)
|
|
75
150
|
{
|
|
76
151
|
NSURL* url = [NSURL fileURLWithPath: [NSString stringWithUTF8String: path]];
|
|
77
152
|
if (!url)
|
|
@@ -95,33 +170,167 @@ namespace Rays
|
|
|
95
170
|
video_track = [track retain];
|
|
96
171
|
}
|
|
97
172
|
|
|
98
|
-
~
|
|
173
|
+
~VideoFileDecoder ()
|
|
99
174
|
{
|
|
175
|
+
stop_reading();
|
|
100
176
|
[video_track release];
|
|
101
177
|
[asset release];
|
|
102
178
|
}
|
|
103
179
|
|
|
104
|
-
|
|
180
|
+
void get_bitmap (Bitmap* bitmap, size_t index) override
|
|
105
181
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
generator.requestedTimeToleranceBefore = kCMTimeZero;
|
|
110
|
-
generator.requestedTimeToleranceAfter = kCMTimeZero;
|
|
182
|
+
ssize_t sindex = (ssize_t) index;
|
|
183
|
+
if (next_index < 0 || sindex < next_index || sindex - next_index > SKIP_MAX)
|
|
184
|
+
start_reading(frame_time(index));
|
|
111
185
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
186
|
+
CMSampleBufferPtr sample = read_sample(sindex);
|
|
187
|
+
if (!sample && reader.status == AVAssetReaderStatusFailed)
|
|
188
|
+
{
|
|
189
|
+
// a failed reader can not be reused, so try once more from scratch
|
|
190
|
+
start_reading(frame_time(index));
|
|
191
|
+
sample = read_sample(sindex);
|
|
192
|
+
}
|
|
193
|
+
if (!sample && reader.status == AVAssetReaderStatusCompleted)
|
|
194
|
+
{
|
|
195
|
+
// size() is an estimate, so an index past the end shows the last frame
|
|
196
|
+
start_reading(last_frame_time());
|
|
197
|
+
sample = read_sample(sindex);
|
|
198
|
+
}
|
|
199
|
+
if (!sample)
|
|
118
200
|
{
|
|
119
201
|
rays_error(
|
|
120
202
|
__FILE__, __LINE__, "failed to decode frame %zu: %s",
|
|
121
|
-
index,
|
|
203
|
+
index, reader_error());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
copy_pixels(bitmap, sample.get());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void start_reading (CMTime time)
|
|
210
|
+
{
|
|
211
|
+
stop_reading();
|
|
212
|
+
|
|
213
|
+
NSError* error = nil;
|
|
214
|
+
AVAssetReader* reader_ =
|
|
215
|
+
[[[AVAssetReader alloc] initWithAsset: asset error: &error] autorelease];
|
|
216
|
+
if (!reader_ || error)
|
|
217
|
+
{
|
|
218
|
+
rays_error(
|
|
219
|
+
__FILE__, __LINE__, "failed to create AVAssetReader: %s",
|
|
220
|
+
error ? error.localizedDescription.UTF8String : "unknown");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
NSDictionary* settings =
|
|
224
|
+
@{
|
|
225
|
+
(NSString*) kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)
|
|
226
|
+
};
|
|
227
|
+
AVAssetReaderOutput* output_ = nil;
|
|
228
|
+
if (CGAffineTransformIsIdentity(video_track.preferredTransform))
|
|
229
|
+
{
|
|
230
|
+
AVAssetReaderTrackOutput* track_output = [AVAssetReaderTrackOutput
|
|
231
|
+
assetReaderTrackOutputWithTrack: video_track outputSettings: settings];
|
|
232
|
+
track_output.alwaysCopiesSampleData = NO;
|
|
233
|
+
output_ = track_output;
|
|
234
|
+
}
|
|
235
|
+
else
|
|
236
|
+
{
|
|
237
|
+
// let a composition apply the track's rotation to each frame
|
|
238
|
+
AVAssetReaderVideoCompositionOutput* composition_output =
|
|
239
|
+
[AVAssetReaderVideoCompositionOutput
|
|
240
|
+
assetReaderVideoCompositionOutputWithVideoTracks: @[video_track]
|
|
241
|
+
videoSettings: settings];
|
|
242
|
+
composition_output.videoComposition =
|
|
243
|
+
[AVMutableVideoComposition videoCompositionWithPropertiesOfAsset: asset];
|
|
244
|
+
composition_output.alwaysCopiesSampleData = NO;
|
|
245
|
+
output_ = composition_output;
|
|
246
|
+
}
|
|
247
|
+
if (![reader_ canAddOutput: output_])
|
|
248
|
+
rays_error(__FILE__, __LINE__, "cannot add output to AVAssetReader");
|
|
249
|
+
[reader_ addOutput: output_];
|
|
250
|
+
|
|
251
|
+
reader_.timeRange = CMTimeRangeMake(time, kCMTimePositiveInfinity);
|
|
252
|
+
if (![reader_ startReading])
|
|
253
|
+
{
|
|
254
|
+
NSString* desc = reader_.error.localizedDescription;
|
|
255
|
+
rays_error(
|
|
256
|
+
__FILE__, __LINE__, "failed to start reading: %s",
|
|
257
|
+
desc ? desc.UTF8String : "unknown");
|
|
122
258
|
}
|
|
123
259
|
|
|
124
|
-
|
|
260
|
+
reader = [reader_ retain];
|
|
261
|
+
output = [output_ retain];
|
|
262
|
+
next_index = frame_index(time);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
void stop_reading ()
|
|
266
|
+
{
|
|
267
|
+
if (reader) [reader cancelReading];
|
|
268
|
+
[output release];
|
|
269
|
+
[reader release];
|
|
270
|
+
output = nil;
|
|
271
|
+
reader = nil;
|
|
272
|
+
next_index = -1;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
CMSampleBufferPtr read_sample (ssize_t index)
|
|
276
|
+
{
|
|
277
|
+
// Reads on until the sample shown at 'index', or the last one when the
|
|
278
|
+
// stream ends before that. NULL on failure.
|
|
279
|
+
|
|
280
|
+
CMSampleBufferPtr last;
|
|
281
|
+
while (true)
|
|
282
|
+
{
|
|
283
|
+
CMSampleBufferPtr sample([output copyNextSampleBuffer], Xot::safe_cfrelease);
|
|
284
|
+
if (!sample)
|
|
285
|
+
return reader.status == AVAssetReaderStatusCompleted ? last : NULL;
|
|
286
|
+
|
|
287
|
+
if (!CMSampleBufferGetImageBuffer(sample.get()))
|
|
288
|
+
continue;
|
|
289
|
+
|
|
290
|
+
ssize_t sample_index =
|
|
291
|
+
frame_index(CMSampleBufferGetPresentationTimeStamp(sample.get()));
|
|
292
|
+
next_index = sample_index + 1;
|
|
293
|
+
if (sample_index >= index) return sample;
|
|
294
|
+
|
|
295
|
+
last = sample;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
CMTime frame_duration () const
|
|
300
|
+
{
|
|
301
|
+
CMTime duration = video_track.minFrameDuration;
|
|
302
|
+
if (CMTIME_IS_VALID(duration) && duration.value > 0)
|
|
303
|
+
return duration;
|
|
304
|
+
|
|
305
|
+
return CMTimeMakeWithSeconds(1 / fps(), 600);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
CMTime frame_time (size_t index) const
|
|
309
|
+
{
|
|
310
|
+
return CMTimeMultiply(frame_duration(), (int32_t) index);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
CMTime last_frame_time () const
|
|
314
|
+
{
|
|
315
|
+
CMTime end = CMTimeRangeGetEnd(video_track.timeRange);
|
|
316
|
+
return CMTimeMaximum(CMTimeSubtract(end, frame_duration()), kCMTimeZero);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
ssize_t frame_index (CMTime time) const
|
|
320
|
+
{
|
|
321
|
+
// A frame that straddles the start of the time range comes out with its
|
|
322
|
+
// time clipped to the start, so truncate rather than round
|
|
323
|
+
|
|
324
|
+
CMTime duration = frame_duration();
|
|
325
|
+
CMTime t = CMTimeConvertScale(
|
|
326
|
+
time, duration.timescale, kCMTimeRoundingMethod_RoundTowardZero);
|
|
327
|
+
return (ssize_t) (t.value / duration.value);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const char* reader_error () const
|
|
331
|
+
{
|
|
332
|
+
NSString* desc = reader ? reader.error.localizedDescription : nil;
|
|
333
|
+
return desc ? desc.UTF8String : "unknown";
|
|
125
334
|
}
|
|
126
335
|
|
|
127
336
|
VideoAudioInList get_audio_tracks () const override
|
|
@@ -138,14 +347,22 @@ namespace Rays
|
|
|
138
347
|
return list;
|
|
139
348
|
}
|
|
140
349
|
|
|
350
|
+
CGSize frame_size () const
|
|
351
|
+
{
|
|
352
|
+
// the size after the track's rotation is applied
|
|
353
|
+
CGSize size = CGSizeApplyAffineTransform(
|
|
354
|
+
video_track.naturalSize, video_track.preferredTransform);
|
|
355
|
+
return CGSizeMake(fabs(size.width), fabs(size.height));
|
|
356
|
+
}
|
|
357
|
+
|
|
141
358
|
coord width () const override
|
|
142
359
|
{
|
|
143
|
-
return (int)
|
|
360
|
+
return (int) std::round(frame_size().width);
|
|
144
361
|
}
|
|
145
362
|
|
|
146
363
|
coord height () const override
|
|
147
364
|
{
|
|
148
|
-
return (int)
|
|
365
|
+
return (int) std::round(frame_size().height);
|
|
149
366
|
}
|
|
150
367
|
|
|
151
368
|
float fps () const override
|
|
@@ -164,10 +381,10 @@ namespace Rays
|
|
|
164
381
|
return asset && video_track && video_track.nominalFrameRate > 0;
|
|
165
382
|
}
|
|
166
383
|
|
|
167
|
-
};//
|
|
384
|
+
};// VideoFileDecoder
|
|
168
385
|
|
|
169
386
|
|
|
170
|
-
struct
|
|
387
|
+
struct GIFFileDecoder : VideoDecoder::Data
|
|
171
388
|
{
|
|
172
389
|
|
|
173
390
|
enum {DEFAULT_FPS = 10};
|
|
@@ -178,7 +395,7 @@ namespace Rays
|
|
|
178
395
|
|
|
179
396
|
float fps_ = 0;
|
|
180
397
|
|
|
181
|
-
|
|
398
|
+
GIFFileDecoder (const char* path)
|
|
182
399
|
{
|
|
183
400
|
NSURL* url = [NSURL fileURLWithPath: [NSString stringWithUTF8String: path]];
|
|
184
401
|
if (!url)
|
|
@@ -186,7 +403,7 @@ namespace Rays
|
|
|
186
403
|
|
|
187
404
|
std::shared_ptr<CGImageSource> source(
|
|
188
405
|
CGImageSourceCreateWithURL((CFURLRef) url, NULL),
|
|
189
|
-
|
|
406
|
+
Xot::safe_cfrelease);
|
|
190
407
|
if (!source)
|
|
191
408
|
rays_error(__FILE__, __LINE__, "failed to create CGImageSource");
|
|
192
409
|
|
|
@@ -207,7 +424,7 @@ namespace Rays
|
|
|
207
424
|
this->fps_ = delay > 0 ? std::round(1 / delay) : (float) DEFAULT_FPS;
|
|
208
425
|
}
|
|
209
426
|
|
|
210
|
-
|
|
427
|
+
void get_bitmap (Bitmap* bitmap, size_t index) override
|
|
211
428
|
{
|
|
212
429
|
std::shared_ptr<CGImage> cgimage(
|
|
213
430
|
CGImageSourceCreateImageAtIndex(source.get(), index, NULL),
|
|
@@ -218,7 +435,7 @@ namespace Rays
|
|
|
218
435
|
__FILE__, __LINE__, "failed to decode GIF frame %zu", index);
|
|
219
436
|
}
|
|
220
437
|
|
|
221
|
-
|
|
438
|
+
copy_pixels(bitmap, cgimage.get());
|
|
222
439
|
}
|
|
223
440
|
|
|
224
441
|
coord width () const override
|
|
@@ -250,7 +467,7 @@ namespace Rays
|
|
|
250
467
|
{
|
|
251
468
|
std::shared_ptr<const __CFDictionary> props(
|
|
252
469
|
CGImageSourceCopyPropertiesAtIndex(source.get(), index, NULL),
|
|
253
|
-
|
|
470
|
+
Xot::safe_cfrelease);
|
|
254
471
|
if (!props)
|
|
255
472
|
return 0;
|
|
256
473
|
|
|
@@ -280,7 +497,7 @@ namespace Rays
|
|
|
280
497
|
return 0;
|
|
281
498
|
}
|
|
282
499
|
|
|
283
|
-
};//
|
|
500
|
+
};// GIFFileDecoder
|
|
284
501
|
|
|
285
502
|
|
|
286
503
|
static bool
|
|
@@ -290,74 +507,83 @@ namespace Rays
|
|
|
290
507
|
}
|
|
291
508
|
|
|
292
509
|
|
|
293
|
-
|
|
510
|
+
VideoDecoder::VideoDecoder ()
|
|
294
511
|
: self(NULL)
|
|
295
512
|
{
|
|
296
513
|
}
|
|
297
514
|
|
|
298
|
-
|
|
515
|
+
VideoDecoder::VideoDecoder (const char* path)
|
|
299
516
|
: self(NULL)
|
|
300
517
|
{
|
|
301
518
|
if (!path || *path == '\0')
|
|
302
519
|
argument_error(__FILE__, __LINE__, "path is empty");
|
|
303
520
|
|
|
304
521
|
if (is_gif_path(path))
|
|
305
|
-
self.reset(new
|
|
522
|
+
self.reset(new GIFFileDecoder(path));
|
|
306
523
|
else
|
|
307
|
-
self.reset(new
|
|
524
|
+
self.reset(new VideoFileDecoder(path));
|
|
525
|
+
|
|
526
|
+
self->path = path;
|
|
308
527
|
}
|
|
309
528
|
|
|
310
|
-
|
|
311
|
-
|
|
529
|
+
void
|
|
530
|
+
VideoDecoder::get_bitmap (Bitmap* bitmap, size_t index)
|
|
312
531
|
{
|
|
313
532
|
if (!*this)
|
|
314
533
|
invalid_state_error(__FILE__, __LINE__);
|
|
315
534
|
|
|
316
|
-
|
|
535
|
+
self->get_bitmap(bitmap, index);
|
|
317
536
|
}
|
|
318
537
|
|
|
319
538
|
VideoAudioInList
|
|
320
|
-
|
|
539
|
+
VideoDecoder::get_audio_tracks () const
|
|
321
540
|
{
|
|
322
541
|
if (!*this) return {};
|
|
323
542
|
return self->get_audio_tracks();
|
|
324
543
|
}
|
|
325
544
|
|
|
545
|
+
const char*
|
|
546
|
+
VideoDecoder::path () const
|
|
547
|
+
{
|
|
548
|
+
if (!*this) return "";
|
|
549
|
+
return self->path.c_str();
|
|
550
|
+
}
|
|
551
|
+
|
|
326
552
|
coord
|
|
327
|
-
|
|
553
|
+
VideoDecoder::width () const
|
|
328
554
|
{
|
|
329
555
|
if (!*this) return 0;
|
|
330
556
|
return self->width();
|
|
331
557
|
}
|
|
332
558
|
|
|
333
559
|
coord
|
|
334
|
-
|
|
560
|
+
VideoDecoder::height () const
|
|
335
561
|
{
|
|
336
562
|
if (!*this) return 0;
|
|
337
563
|
return self->height();
|
|
338
564
|
}
|
|
339
565
|
|
|
340
566
|
float
|
|
341
|
-
|
|
567
|
+
VideoDecoder::fps () const
|
|
342
568
|
{
|
|
343
569
|
if (!*this) return 0;
|
|
344
570
|
return self->fps();
|
|
345
571
|
}
|
|
346
572
|
|
|
347
573
|
size_t
|
|
348
|
-
|
|
574
|
+
VideoDecoder::size () const
|
|
349
575
|
{
|
|
350
576
|
if (!*this) return 0;
|
|
351
577
|
return self->size();
|
|
352
578
|
}
|
|
353
579
|
|
|
354
|
-
|
|
580
|
+
VideoDecoder::operator bool () const
|
|
355
581
|
{
|
|
356
582
|
return self && *self;
|
|
357
583
|
}
|
|
358
584
|
|
|
359
585
|
bool
|
|
360
|
-
|
|
586
|
+
VideoDecoder::operator ! () const
|
|
361
587
|
{
|
|
362
588
|
return !operator bool();
|
|
363
589
|
}
|
|
@@ -501,7 +727,7 @@ namespace Rays
|
|
|
501
727
|
|
|
502
728
|
std::shared_ptr<CGImageDestination> dest(
|
|
503
729
|
CGImageDestinationCreateWithURL((CFURLRef) url, kUTTypeGIF, video.size(), NULL),
|
|
504
|
-
|
|
730
|
+
Xot::safe_cfrelease);
|
|
505
731
|
if (!dest)
|
|
506
732
|
rays_error(__FILE__, __LINE__, "CGImageDestinationCreateWithURL() failed");
|
|
507
733
|
|
data/src/osx/video_audio_in.mm
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
#import <AVFoundation/AVFoundation.h>
|
|
6
|
+
#include <xot/util.h>
|
|
6
7
|
#include "rays/exception.h"
|
|
7
8
|
|
|
8
9
|
|
|
@@ -134,7 +135,7 @@ namespace Rays
|
|
|
134
135
|
|
|
135
136
|
std::shared_ptr<opaqueCMSampleBuffer> samples(
|
|
136
137
|
[output copyNextSampleBuffer],
|
|
137
|
-
|
|
138
|
+
Xot::safe_cfrelease);
|
|
138
139
|
if (!samples)
|
|
139
140
|
return false;
|
|
140
141
|
|
data/src/sdl/video.cpp
CHANGED
|
@@ -8,63 +8,69 @@ namespace Rays
|
|
|
8
8
|
{
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
struct
|
|
11
|
+
struct VideoDecoder::Data
|
|
12
12
|
{
|
|
13
|
-
};//
|
|
13
|
+
};// VideoDecoder::Data
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
VideoDecoder::VideoDecoder ()
|
|
17
17
|
{
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
VideoDecoder::VideoDecoder (const char*)
|
|
21
21
|
{
|
|
22
22
|
not_implemented_error(__FILE__, __LINE__);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
void
|
|
26
|
+
VideoDecoder::get_bitmap (Bitmap*, size_t)
|
|
27
27
|
{
|
|
28
28
|
not_implemented_error(__FILE__, __LINE__);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
VideoAudioInList
|
|
32
|
-
|
|
32
|
+
VideoDecoder::get_audio_tracks () const
|
|
33
33
|
{
|
|
34
34
|
return {};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const char*
|
|
38
|
+
VideoDecoder::path () const
|
|
39
|
+
{
|
|
40
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
coord
|
|
38
|
-
|
|
44
|
+
VideoDecoder::width () const
|
|
39
45
|
{
|
|
40
46
|
return 0;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
coord
|
|
44
|
-
|
|
50
|
+
VideoDecoder::height () const
|
|
45
51
|
{
|
|
46
52
|
return 0;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
float
|
|
50
|
-
|
|
56
|
+
VideoDecoder::fps () const
|
|
51
57
|
{
|
|
52
58
|
return 0;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
size_t
|
|
56
|
-
|
|
62
|
+
VideoDecoder::size () const
|
|
57
63
|
{
|
|
58
64
|
return 0;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
VideoDecoder::operator bool () const
|
|
62
68
|
{
|
|
63
69
|
return false;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
bool
|
|
67
|
-
|
|
73
|
+
VideoDecoder::operator ! () const
|
|
68
74
|
{
|
|
69
75
|
return !operator bool();
|
|
70
76
|
}
|