rays-video 0.1.2

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.
@@ -0,0 +1,252 @@
1
+ // -*- mode: objc -*-
2
+ #import "video_audio_in.h"
3
+
4
+
5
+ #import <AVFoundation/AVFoundation.h>
6
+ #include "rays/exception.h"
7
+
8
+
9
+ namespace Rays
10
+ {
11
+
12
+
13
+ struct VideoAudioIn::Data
14
+ {
15
+
16
+ AVAsset* asset = nil;
17
+
18
+ AVAssetTrack* audio_track = nil;
19
+
20
+ uint nsamples = 0;
21
+
22
+ Beeps::Signals buffer;
23
+
24
+ uint buffer_offset = 0;
25
+
26
+ AVAssetReader* reader = nil;
27
+
28
+ AVAssetReaderAudioMixOutput* output = nil;
29
+
30
+ Data (AVAsset* asset, AVAssetTrack* audio_track)
31
+ {
32
+ assert(asset && audio_track);
33
+
34
+ CMFormatDescriptionRef format =
35
+ (__bridge CMFormatDescriptionRef) audio_track.formatDescriptions.firstObject;
36
+ if (!format)
37
+ rays_error(__FILE__, __LINE__, "failed to get CMFormatDescription");
38
+
39
+ const AudioStreamBasicDescription* desc =
40
+ CMAudioFormatDescriptionGetStreamBasicDescription(format);
41
+ if (!desc)
42
+ rays_error(__FILE__, __LINE__, "failed to get AudioStreamBasicDescription");
43
+
44
+ this->asset = [asset retain];
45
+ this->audio_track = [audio_track retain];
46
+ uint nchannels = std::min<uint>(desc->mChannelsPerFrame, 2);
47
+ this->buffer = Beeps::Signals(2048, nchannels, desc->mSampleRate);
48
+ double duration = CMTimeGetSeconds(audio_track.timeRange.duration);
49
+ this->nsamples = (uint) (duration * buffer.sample_rate());
50
+ }
51
+
52
+ ~Data ()
53
+ {
54
+ clear_reader();
55
+ [audio_track release];
56
+ [asset release];
57
+ }
58
+
59
+ void create_reader (uint offset)
60
+ {
61
+ clear_reader();
62
+
63
+ NSError* error = nil;
64
+ AVAssetReader* reader =
65
+ [[[AVAssetReader alloc] initWithAsset: asset error: &error] autorelease];
66
+ if (!reader || error)
67
+ rays_error(__FILE__, __LINE__, "failed to create AVAssetReader");
68
+
69
+ AVAssetReaderAudioMixOutput* output = [AVAssetReaderAudioMixOutput
70
+ assetReaderAudioMixOutputWithAudioTracks: @[audio_track]
71
+ audioSettings: @{
72
+ AVFormatIDKey: @(kAudioFormatLinearPCM),
73
+ AVLinearPCMBitDepthKey: @32,
74
+ AVLinearPCMIsFloatKey: @YES,
75
+ AVLinearPCMIsNonInterleaved: @YES,
76
+ AVNumberOfChannelsKey: @(buffer.nchannels()),
77
+ }];
78
+ if (![reader canAddOutput: output])
79
+ rays_error(__FILE__, __LINE__, "cannot add audio output");
80
+
81
+ [reader addOutput: output];
82
+ reader.timeRange = CMTimeRangeMake(
83
+ CMTimeMakeWithSeconds(
84
+ (double) offset / buffer.sample_rate(),
85
+ (int32_t) buffer.sample_rate()),
86
+ kCMTimePositiveInfinity);
87
+ if (![reader startReading])
88
+ rays_error(__FILE__, __LINE__, "failed to start reading audio");
89
+
90
+ this->reader = [reader retain];
91
+ this->output = [output retain];
92
+ this->buffer_offset = offset;
93
+ this->buffer .clear();
94
+ }
95
+
96
+ void clear_reader ()
97
+ {
98
+ if (reader && reader.status == AVAssetReaderStatusReading)
99
+ [reader cancelReading];
100
+
101
+ [output release];
102
+ output = nil;
103
+ [reader release];
104
+ reader = nil;
105
+ }
106
+
107
+ bool read_next (Beeps::Signals* signals, uint* offset)
108
+ {
109
+ assert(signals && offset);
110
+
111
+ if (
112
+ !reader ||
113
+ *offset < buffer_offset ||
114
+ *offset > buffer_offset + buffer.nsamples())
115
+ {
116
+ create_reader(*offset);
117
+ }
118
+
119
+ if (*offset == buffer_offset + buffer.nsamples())
120
+ {
121
+ if (!read_next_buffer()) return false;
122
+ buffer_offset = *offset;
123
+ }
124
+
125
+ uint size = signals->append(buffer, *offset - buffer_offset);
126
+ *offset += size;
127
+ return size > 0;
128
+ }
129
+
130
+ bool read_next_buffer ()
131
+ {
132
+ if (!reader || !output || reader.status != AVAssetReaderStatusReading)
133
+ return false;
134
+
135
+ std::shared_ptr<opaqueCMSampleBuffer> samples(
136
+ [output copyNextSampleBuffer],
137
+ CFRelease);
138
+ if (!samples)
139
+ return false;
140
+
141
+ uint nsamples = (uint) CMSampleBufferGetNumSamples(samples.get());
142
+ if (nsamples <= 0)
143
+ return false;
144
+
145
+ CMBlockBufferRef block = CMSampleBufferGetDataBuffer(samples.get());
146
+ if (!block)
147
+ return false;
148
+
149
+ size_t size = CMBlockBufferGetDataLength(block);
150
+ char* data = NULL;
151
+ CMBlockBufferGetDataPointer(block, 0, NULL, &size, &data);
152
+ if (!data || size <= 0)
153
+ rays_error(__FILE__, __LINE__);
154
+
155
+ CMFormatDescriptionRef format =
156
+ CMSampleBufferGetFormatDescription(samples.get());
157
+ if (!format)
158
+ rays_error(__FILE__, __LINE__);
159
+
160
+ const AudioStreamBasicDescription* desc =
161
+ CMAudioFormatDescriptionGetStreamBasicDescription(format);
162
+ if (!desc)
163
+ rays_error(__FILE__, __LINE__);
164
+
165
+ uint nchannels = desc->mChannelsPerFrame;
166
+ if (nchannels != buffer.nchannels())
167
+ rays_error(__FILE__, __LINE__);
168
+
169
+ std::vector<const float*> channels(nchannels);
170
+ for (uint ch = 0; ch < nchannels; ++ch)
171
+ channels[ch] = (const float*) data + ch * nsamples;
172
+
173
+ buffer.clear(nsamples);
174
+ buffer.append(channels.data(), nsamples, nchannels, buffer.sample_rate());
175
+ return buffer.nsamples() > 0;
176
+ }
177
+
178
+ };// VideoAudioIn::Data
179
+
180
+
181
+ VideoAudioIn::Data*
182
+ VideoAudioIn_Data_create (AVAsset* asset, AVAssetTrack* audio_track)
183
+ {
184
+ return new VideoAudioIn::Data(asset, audio_track);
185
+ }
186
+
187
+
188
+ VideoAudioIn::VideoAudioIn (Data* data)
189
+ : self(data)
190
+ {
191
+ }
192
+
193
+ VideoAudioIn::~VideoAudioIn ()
194
+ {
195
+ }
196
+
197
+ double
198
+ VideoAudioIn::sample_rate () const
199
+ {
200
+ return self->buffer.sample_rate();
201
+ }
202
+
203
+ uint
204
+ VideoAudioIn::nchannels () const
205
+ {
206
+ return self->buffer.nchannels();
207
+ }
208
+
209
+ uint
210
+ VideoAudioIn::nsamples () const
211
+ {
212
+ return self->nsamples;
213
+ }
214
+
215
+ float
216
+ VideoAudioIn::seconds () const
217
+ {
218
+ if (this->sample_rate() <= 0)
219
+ return 0;
220
+
221
+ return (float) (self->nsamples / this->sample_rate());
222
+ }
223
+
224
+ VideoAudioIn::operator bool () const
225
+ {
226
+ return
227
+ Super::operator bool() &&
228
+ self->asset &&
229
+ self->audio_track &&
230
+ self->buffer;
231
+ }
232
+
233
+ bool
234
+ VideoAudioIn::seekable () const
235
+ {
236
+ return true;
237
+ }
238
+
239
+ void
240
+ VideoAudioIn::generate (Context* context, Beeps::Signals* signals, uint* offset)
241
+ {
242
+ Super::generate(context, signals, offset);
243
+
244
+ while (!signals->full())
245
+ {
246
+ if (!self->read_next(signals, offset))
247
+ break;
248
+ }
249
+ }
250
+
251
+
252
+ }// Rays
data/src/sdl/video.cpp ADDED
@@ -0,0 +1,86 @@
1
+ #include "../video.h"
2
+
3
+
4
+ #include "rays/exception.h"
5
+
6
+
7
+ namespace Rays
8
+ {
9
+
10
+
11
+ struct VideoReader::Data
12
+ {
13
+ };// VideoReader::Data
14
+
15
+
16
+ VideoReader::VideoReader ()
17
+ {
18
+ }
19
+
20
+ VideoReader::VideoReader (const char*)
21
+ {
22
+ not_implemented_error(__FILE__, __LINE__);
23
+ }
24
+
25
+ Image
26
+ VideoReader::decode_image (size_t, float) const
27
+ {
28
+ not_implemented_error(__FILE__, __LINE__);
29
+ }
30
+
31
+ VideoAudioInList
32
+ VideoReader::get_audio_tracks () const
33
+ {
34
+ return {};
35
+ }
36
+
37
+ coord
38
+ VideoReader::width () const
39
+ {
40
+ return 0;
41
+ }
42
+
43
+ coord
44
+ VideoReader::height () const
45
+ {
46
+ return 0;
47
+ }
48
+
49
+ float
50
+ VideoReader::fps () const
51
+ {
52
+ return 0;
53
+ }
54
+
55
+ size_t
56
+ VideoReader::size () const
57
+ {
58
+ return 0;
59
+ }
60
+
61
+ VideoReader::operator bool () const
62
+ {
63
+ return false;
64
+ }
65
+
66
+ bool
67
+ VideoReader::operator ! () const
68
+ {
69
+ return !operator bool();
70
+ }
71
+
72
+
73
+ void
74
+ Video::save (const char*)
75
+ {
76
+ not_implemented_error(__FILE__, __LINE__);
77
+ }
78
+
79
+ const StringList&
80
+ get_video_exts ()
81
+ {
82
+ not_implemented_error(__FILE__, __LINE__);
83
+ }
84
+
85
+
86
+ }// Rays
@@ -0,0 +1,63 @@
1
+ #include "../video_audio_in.h"
2
+
3
+
4
+ namespace Rays
5
+ {
6
+
7
+
8
+ struct VideoAudioIn::Data
9
+ {
10
+ };// VideoAudioIn::Data
11
+
12
+
13
+ VideoAudioIn::VideoAudioIn (Data* data)
14
+ : self(data)
15
+ {
16
+ }
17
+
18
+ VideoAudioIn::~VideoAudioIn ()
19
+ {
20
+ }
21
+
22
+ double
23
+ VideoAudioIn::sample_rate () const
24
+ {
25
+ return 0;
26
+ }
27
+
28
+ uint
29
+ VideoAudioIn::nchannels () const
30
+ {
31
+ return 0;
32
+ }
33
+
34
+ uint
35
+ VideoAudioIn::nsamples () const
36
+ {
37
+ return 0;
38
+ }
39
+
40
+ float
41
+ VideoAudioIn::seconds () const
42
+ {
43
+ return 0;
44
+ }
45
+
46
+ VideoAudioIn::operator bool () const
47
+ {
48
+ return false;
49
+ }
50
+
51
+ bool
52
+ VideoAudioIn::seekable () const
53
+ {
54
+ return false;
55
+ }
56
+
57
+ void
58
+ VideoAudioIn::generate (Context* context, Beeps::Signals* signals, uint* offset)
59
+ {
60
+ }
61
+
62
+
63
+ }// Rays
data/src/video.cpp ADDED
@@ -0,0 +1,278 @@
1
+ #include "video.h"
2
+
3
+
4
+ #include <xot/util.h>
5
+ #include <beeps/sound.h>
6
+ #include "rays/bitmap.h"
7
+ #include "rays/exception.h"
8
+
9
+
10
+ namespace Rays
11
+ {
12
+
13
+
14
+ struct Video::Data
15
+ {
16
+
17
+ int width = 0, height = 0;
18
+
19
+ float fps = 0, pixel_density = 1;
20
+
21
+ size_t position = 0;
22
+
23
+ std::vector<Image> images;
24
+
25
+ VideoAudioInList audio_tracks;
26
+
27
+ Beeps::SoundPlayer player;
28
+
29
+ };// Video::Data
30
+
31
+
32
+ struct VideoImageData : public Image::Data
33
+ {
34
+
35
+ VideoReader reader;
36
+
37
+ size_t index;
38
+
39
+ VideoImageData (const VideoReader& reader, size_t index)
40
+ : reader(reader), index(index)
41
+ {
42
+ }
43
+
44
+ void preprocess (const Image* image) const override
45
+ {
46
+ Image decoded = reader.decode_image(index, 1);
47
+ Bitmap bitmap = decoded.bitmap();
48
+ if (bitmap) Xot::hint_memory_usage(bitmap.size());
49
+ const_cast<Image*>(image)->self = decoded.self;
50
+ }
51
+
52
+ };// VideoImageData
53
+
54
+
55
+ Video
56
+ load_video (const char* path)
57
+ {
58
+ VideoReader reader(path);
59
+ if (!reader)
60
+ invalid_state_error(__FILE__, __LINE__);
61
+
62
+ Video video;
63
+ Video::Data* self = video.self.get();
64
+ self->width = reader.width();
65
+ self->height = reader.height();
66
+ self->fps = reader.fps();
67
+ self->pixel_density = 1;
68
+ self->position = 0;
69
+
70
+ size_t size = reader.size();
71
+ self->images.reserve(size);
72
+ for (size_t i = 0; i < size; ++i)
73
+ self->images.push_back(Image(new VideoImageData(reader, i)));
74
+
75
+ self->audio_tracks = reader.get_audio_tracks();
76
+
77
+ return video;
78
+ }
79
+
80
+
81
+ Video::Video ()
82
+ {
83
+ }
84
+
85
+ Video::Video (int width, int height, float fps, float pixel_density)
86
+ {
87
+ if (width <= 0)
88
+ argument_error(__FILE__, __LINE__, "width must be > 0");
89
+ if (height <= 0)
90
+ argument_error(__FILE__, __LINE__, "height must be > 0");
91
+ if (fps <= 0)
92
+ argument_error(__FILE__, __LINE__, "fps must be > 0");
93
+ if (pixel_density <= 0)
94
+ argument_error(__FILE__, __LINE__, "pixel_density must be > 0");
95
+
96
+ self->width = width;
97
+ self->height = height;
98
+ self->fps = fps;
99
+ self->pixel_density = pixel_density;
100
+ }
101
+
102
+ Video::~Video ()
103
+ {
104
+ }
105
+
106
+ Video
107
+ Video::dup () const
108
+ {
109
+ Video v;
110
+ *v.self = *self;
111
+ return v;
112
+ }
113
+
114
+ void
115
+ Video::insert (size_t index, const Image& image)
116
+ {
117
+ if (!*this)
118
+ invalid_state_error(__FILE__, __LINE__, "video is not initialized");
119
+
120
+ self->images.insert(self->images.begin() + index, image);
121
+ }
122
+
123
+ void
124
+ Video::append (const Image& image)
125
+ {
126
+ insert(size(), image);
127
+ }
128
+
129
+ void
130
+ Video::remove (size_t index)
131
+ {
132
+ if (index >= size()) return;
133
+ self->images.erase(self->images.begin() + index);
134
+ }
135
+
136
+ void
137
+ Video::play ()
138
+ {
139
+ if (empty())
140
+ invalid_state_error(__FILE__, __LINE__, "video is empty");
141
+
142
+ if (self->audio_tracks.empty())
143
+ invalid_state_error(__FILE__, __LINE__, "playing video without audio is not yet supported");
144
+
145
+ VideoAudioIn* in = self->audio_tracks[0].get();
146
+ self->player = Beeps::Sound(in, 0, in->nchannels(), in->sample_rate()).play();
147
+ }
148
+
149
+ void
150
+ Video::pause ()
151
+ {
152
+ if (self->player) self->player.pause();
153
+ }
154
+
155
+ void
156
+ Video::stop ()
157
+ {
158
+ if (self->player) self->player.stop();
159
+ }
160
+
161
+ void
162
+ Video::set_time_scale (float scale)
163
+ {
164
+ if (self->player) self->player.set_time_scale(scale);
165
+ }
166
+
167
+ float
168
+ Video::time_scale () const
169
+ {
170
+ return self->player ? self->player.time_scale() : 1;
171
+ }
172
+
173
+ coord
174
+ Video::width () const
175
+ {
176
+ return self->width;
177
+ }
178
+
179
+ coord
180
+ Video::height () const
181
+ {
182
+ return self->height;
183
+ }
184
+
185
+ float
186
+ Video::fps () const
187
+ {
188
+ return self->fps;
189
+ }
190
+
191
+ float
192
+ Video::pixel_density () const
193
+ {
194
+ return self->pixel_density;
195
+ }
196
+
197
+ size_t
198
+ Video::size () const
199
+ {
200
+ return self->images.size();
201
+ }
202
+
203
+ bool
204
+ Video::empty () const
205
+ {
206
+ return self->images.empty();
207
+ }
208
+
209
+ void
210
+ Video::set_position (size_t index)
211
+ {
212
+ if (empty()) index = 0;
213
+ else if (index >= size()) index = size() - 1;
214
+ self->position = index;
215
+ }
216
+
217
+ size_t
218
+ Video::position () const
219
+ {
220
+ if (empty()) self->position = 0;
221
+ else if (self->position >= size()) self->position = size() - 1;
222
+ return self->position;
223
+ }
224
+
225
+ Video::const_iterator
226
+ Video::begin () const
227
+ {
228
+ return self->images.begin();
229
+ }
230
+
231
+ Video::const_iterator
232
+ Video::end () const
233
+ {
234
+ return self->images.end();
235
+ }
236
+
237
+ Image
238
+ Video::operator [] (size_t index) const
239
+ {
240
+ if (empty())
241
+ {
242
+ index_error(
243
+ __FILE__, __LINE__, "index %zu is out of range (empty)",
244
+ index);
245
+ }
246
+ if (index >= size())
247
+ {
248
+ index_error(
249
+ __FILE__, __LINE__, "index %zu is out of range (0..%zu)",
250
+ index, size() - 1);
251
+ }
252
+ return self->images[index];
253
+ }
254
+
255
+ Video::operator Image () const
256
+ {
257
+ if (self->player)
258
+ {
259
+ size_t index = (size_t) (self->player.time() * self->fps);
260
+ if (index >= size()) index = size() - 1;
261
+ self->position = index;
262
+ }
263
+ return operator[](self->position);
264
+ }
265
+
266
+ Video::operator bool () const
267
+ {
268
+ return self->width > 0 && self->height > 0;
269
+ }
270
+
271
+ bool
272
+ Video::operator ! () const
273
+ {
274
+ return !operator bool();
275
+ }
276
+
277
+
278
+ }// Rays
data/src/video.h ADDED
@@ -0,0 +1,50 @@
1
+ // -*- c++ -*-
2
+ #pragma once
3
+ #ifndef __RAYS_VIDEO_SRC_VIDEO_H__
4
+ #define __RAYS_VIDEO_SRC_VIDEO_H__
5
+
6
+
7
+ #include "rays/video.h"
8
+ #include "video_audio_in.h"
9
+
10
+
11
+ namespace Rays
12
+ {
13
+
14
+
15
+ class VideoReader
16
+ {
17
+
18
+ public:
19
+
20
+ VideoReader ();
21
+
22
+ VideoReader (const char* path);
23
+
24
+ Image decode_image (size_t index, float pixel_density) const;
25
+
26
+ VideoAudioInList get_audio_tracks () const;
27
+
28
+ coord width () const;
29
+
30
+ coord height () const;
31
+
32
+ float fps () const;
33
+
34
+ size_t size () const;
35
+
36
+ operator bool () const;
37
+
38
+ bool operator ! () const;
39
+
40
+ struct Data;
41
+
42
+ Xot::PSharedImpl<Data> self;
43
+
44
+ };// VideoReader
45
+
46
+
47
+ }// Rays
48
+
49
+
50
+ #endif//EOH