]> git.sesse.net Git - nageru/blob - decklink_capture.cpp
Add audio support for the DeckLink inputs.
[nageru] / decklink_capture.cpp
1 #include "decklink_capture.h"
2
3 #include <assert.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <cstddef>
9
10 #include <DeckLinkAPI.h>
11 #include <DeckLinkAPIConfiguration.h>
12 #include <DeckLinkAPIDiscovery.h>
13 #include <DeckLinkAPIModes.h>
14 #include "bmusb/bmusb.h"
15
16 #define FRAME_SIZE (8 << 20)  // 8 MB.
17
18 using namespace std;
19 using namespace std::placeholders;
20
21 namespace {
22
23 // TODO: Support stride.
24 // TODO: Support AVX2 (adapt from bmusb).
25 void memcpy_interleaved(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n)
26 {
27         assert(n % 2 == 0);
28         uint8_t *dptr1 = dest1;
29         uint8_t *dptr2 = dest2;
30
31         for (size_t i = 0; i < n; i += 2) {
32                 *dptr1++ = *src++;
33                 *dptr2++ = *src++;
34         }
35 }
36
37 }  // namespace
38
39 DeckLinkCapture::DeckLinkCapture(IDeckLink *card, int card_index)
40 {
41         {
42                 const char *model_name;
43                 char buf[256];
44                 if (card->GetModelName(&model_name) == S_OK) {
45                         snprintf(buf, sizeof(buf), "Card %d: %s", card_index, model_name);
46                 } else {
47                         snprintf(buf, sizeof(buf), "Card %d: Unknown DeckLink card", card_index);
48                 }
49                 description = buf;
50         }
51
52         if (card->QueryInterface(IID_IDeckLinkInput, (void**)&input) != S_OK) {
53                 fprintf(stderr, "Card %d has no inputs\n", card_index);
54                 exit(1);
55         }
56
57         /* Set up the video and audio sources. */
58         IDeckLinkConfiguration *config;
59         if (card->QueryInterface(IID_IDeckLinkConfiguration, (void**)&config) != S_OK) {
60                 fprintf(stderr, "Failed to get configuration interface for card %d\n", card_index);
61                 exit(1);
62         }
63
64         if (config->SetInt(bmdDeckLinkConfigVideoInputConnection, bmdVideoConnectionHDMI) != S_OK) {
65                 fprintf(stderr, "Failed to set video input connection for card %d\n", card_index);
66                 exit(1);
67         }
68
69         if (config->SetInt(bmdDeckLinkConfigAudioInputConnection, bmdAudioConnectionEmbedded) != S_OK) {
70                 fprintf(stderr, "Failed to set video input connection for card %d\n", card_index);
71                 exit(1);
72         }
73
74         // TODO: Make the user mode selectable.
75         BMDDisplayModeSupport support;
76         IDeckLinkDisplayMode *display_mode;
77         if (input->DoesSupportVideoMode(bmdModeHD720p5994, bmdFormat8BitYUV, /*flags=*/0, &support, &display_mode)) {
78                 fprintf(stderr, "Failed to query display mode for card %d\n", card_index);
79                 exit(1);
80         }
81
82         if (support == bmdDisplayModeNotSupported) {
83                 fprintf(stderr, "Card %d does not support display mode\n", card_index);
84                 exit(1);
85         }
86
87         if (display_mode->GetFrameRate(&frame_duration, &time_scale) != S_OK) {
88                 fprintf(stderr, "Could not get frame rate for card %d\n", card_index);
89                 exit(1);
90         }
91
92         if (input->EnableVideoInput(bmdModeHD720p5994, bmdFormat8BitYUV, 0) != S_OK) {
93                 fprintf(stderr, "Failed to set 720p59.94 connection for card %d\n", card_index);
94                 exit(1);
95         }
96
97         if (input->EnableAudioInput(48000, bmdAudioSampleType32bitInteger, 2) != S_OK) {
98                 fprintf(stderr, "Failed to enable audio input for card %d\n", card_index);
99                 exit(1);
100         }
101
102         input->SetCallback(this);
103 }
104
105 DeckLinkCapture::~DeckLinkCapture()
106 {
107         if (has_dequeue_callbacks) {
108                 dequeue_cleanup_callback();
109         }
110 }
111
112 HRESULT STDMETHODCALLTYPE DeckLinkCapture::QueryInterface(REFIID, LPVOID *)
113 {
114         return E_NOINTERFACE;
115 }
116
117 ULONG STDMETHODCALLTYPE DeckLinkCapture::AddRef(void)
118 {
119         return refcount.fetch_add(1) + 1;
120 }
121
122 ULONG STDMETHODCALLTYPE DeckLinkCapture::Release(void)
123 {
124         int new_ref = refcount.fetch_sub(1) - 1;
125         if (new_ref == 0)
126                 delete this;
127         return new_ref;
128 }
129
130 HRESULT STDMETHODCALLTYPE DeckLinkCapture::VideoInputFormatChanged(
131         BMDVideoInputFormatChangedEvents,
132         IDeckLinkDisplayMode* display_mode,
133         BMDDetectedVideoInputFormatFlags)
134 {
135         if (display_mode->GetFrameRate(&frame_duration, &time_scale) != S_OK) {
136                 fprintf(stderr, "Could not get new frame rate\n");
137                 exit(1);
138         }
139         return S_OK;
140 }
141
142 HRESULT STDMETHODCALLTYPE DeckLinkCapture::VideoInputFrameArrived(
143         IDeckLinkVideoInputFrame *video_frame,
144         IDeckLinkAudioInputPacket *audio_frame)
145 {
146         if (!done_init) {
147                 if (has_dequeue_callbacks) {
148                         dequeue_init_callback();
149                 }
150                 done_init = true;
151         }
152
153         FrameAllocator::Frame current_video_frame, current_audio_frame;
154         VideoFormat video_format;
155         AudioFormat audio_format;
156
157         if (video_frame) {
158                 video_format.has_signal = !(video_frame->GetFlags() & bmdFrameHasNoInputSource);
159
160                 int width = video_frame->GetWidth();
161                 int height = video_frame->GetHeight();
162                 const int stride = video_frame->GetRowBytes();
163                 assert(stride == width * 2);
164
165                 current_video_frame = video_frame_allocator->alloc_frame();
166                 if (current_video_frame.data != nullptr) {
167                         const uint8_t *frame_bytes;
168                         video_frame->GetBytes((void **)&frame_bytes);
169
170                         memcpy_interleaved(current_video_frame.data, current_video_frame.data2,
171                                 frame_bytes, width * height * 2);
172                         current_video_frame.len += width * height * 2;
173
174                         video_format.width = width;
175                         video_format.height = height;
176                         video_format.frame_rate_nom = time_scale;
177                         video_format.frame_rate_den = frame_duration;
178                 }
179         }
180
181         if (audio_frame) {
182                 int num_samples = audio_frame->GetSampleFrameCount();
183
184                 current_audio_frame = audio_frame_allocator->alloc_frame();
185                 if (current_audio_frame.data != nullptr) {
186                         const uint8_t *frame_bytes;
187                         audio_frame->GetBytes((void **)&frame_bytes);
188                         current_audio_frame.len = sizeof(int32_t) * 2 * num_samples;
189
190                         memcpy(current_audio_frame.data, frame_bytes, current_audio_frame.len);
191
192                         audio_format.bits_per_sample = 32;
193                         audio_format.num_channels = 2;
194                 }
195         }
196
197         if (current_video_frame.data != nullptr || current_audio_frame.data != nullptr) {
198                 // TODO: Put into a queue and put into a dequeue thread, if the
199                 // BlackMagic drivers don't already do that for us?
200                 frame_callback(timecode,
201                         current_video_frame, /*video_offset=*/0, video_format,
202                         current_audio_frame, /*audio_offset=*/0, audio_format);
203         }
204
205         timecode++;
206         return S_OK;
207 }
208
209 void DeckLinkCapture::configure_card()
210 {
211         if (video_frame_allocator == nullptr) {
212                 set_video_frame_allocator(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));  // FIXME: leak.
213         }
214         if (audio_frame_allocator == nullptr) {
215                 set_audio_frame_allocator(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES));  // FIXME: leak.
216         }
217 }
218
219 void DeckLinkCapture::start_bm_capture()
220 {
221         if (input->StartStreams() != S_OK) {
222                 fprintf(stderr, "StartStreams failed\n");
223                 exit(1);
224         }
225 }
226
227 void DeckLinkCapture::stop_dequeue_thread()
228 {
229         if (input->StopStreams() != S_OK) {
230                 fprintf(stderr, "StopStreams failed\n");
231                 exit(1);
232         }
233 }
234