Skip to content

Commit

Permalink
add support for AJA capture cards
Browse files Browse the repository at this point in the history
  • Loading branch information
lukegb committed Dec 29, 2024
1 parent 1c88dbb commit 5c036b0
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 6 deletions.
4 changes: 2 additions & 2 deletions vocto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
os.environ['GST_DEBUG_DUMP_DOT_DIR'] = os.getcwd()

def kind_has_audio(source):
return source in ["decklink", "tcp", "test", "pa", "alsa"]
return source in ["aja", "decklink", "tcp", "test", "pa", "alsa"]

def kind_has_video(source):
return source in ["decklink", "tcp", "test", "v4l2", "img", "file", "background", "RPICam"]
return source in ["aja", "decklink", "tcp", "test", "v4l2", "img", "file", "background", "RPICam"]
12 changes: 12 additions & 0 deletions vocto/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ def getDeckLinkVideoMode(self, source):

def getDeckLinkVideoFormat(self, source):
return self.get('source.{}'.format(source), 'video_format', fallback='auto')

def getAJADeviceIdentifier(self, source):
return self.get(f'source.{source}', 'device', fallback='')

def getAJAInputChannel(self, source):
return self.getint(f'source.{source}', 'channel', fallback=0)

def getAJAVideoMode(self, source):
return self.get(f'source.{source}', 'video_mode', fallback='auto')

def getAJAAudioSource(self, source):
return self.get(f'source.{source}', 'audio_source', fallback='embedded')

def getPulseAudioDevice(self, source):
return self.get('source.{}'.format(source), 'device', fallback='auto')
Expand Down
63 changes: 59 additions & 4 deletions voctocore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
- [1.6.1.1.4. Decklink Sources](#16114-decklink-sources)
- [1.6.1.1.5. Image Sources](#16115-image-sources)
- [1.6.1.1.6. Video4Linux2 Sources](#16116-video4linux2-sources)
- [1.6.1.1.7. Common Source Attributes](#16117-common-source-attributes)
- [1.6.1.1.7. AJA Sources](#16117-aja-sources)
- [1.6.1.1.8. Common Source Attributes](#16118-common-source-attributes)
- [1.6.1.2. Background Video Source](#1612-background-video-source)
- [1.6.1.2.1. Multiple Background Video Sources (depending on Composite)](#16121-multiple-background-video-sources-depending-on-composite)
- [1.6.1.3. Blinding Sources (Video and Audio)](#1613-blinding-sources-video-and-audio)
Expand All @@ -43,6 +44,9 @@
- [1.6.2.5. Sources Recording](#1625-sources-recording)
- [1.6.2.6. Sources Preview](#1626-sources-preview)
- [1.6.2.7. Mirror Ports](#1627-mirror-ports)
- [1.6.2.8. SRT Server](#1628-srt-server)
- [1.6.2.9. Program Output](#1629-program-output)
- [1.6.2.9.1. AJA Program Output](#16291-aja-program-output)
- [1.6.3. A/V Processing Elements](#163-av-processing-elements)
- [1.6.3.1. DeMux](#1631-demux)
- [1.6.3.2. Mux](#1632-mux)
Expand Down Expand Up @@ -308,7 +312,44 @@ format=YUY2
| `framerate` | `10/1` | 25/1 | video frame rate expected from the source
| `format` | `YUY2` | YUY2 | video format expected from the source

##### 1.6.1.1.7. Common Source Attributes
##### 1.6.1.1.7. AJA Sources

You can use `aja` as a source's `kind` if you would like to grab audio and
video from a [AJA](https://www.aja.com/family/desktop-io) Desktop I/O grabber
card.

```ini
[mix]
sources = cam1,cam2

[source.cam1]
kind = aja
device = 00A00012
channel = 0

[source.cam2]
kind = aja
device = 00A00012
channel = 1
```

You now have two **AJA A/V grabber** sources, using input channels 0 and 1;
this usually maps to the first two SDI inputs.

The `device` attribute must be set, and is usually a truncated part of the
serial number. If set to `?`, then a list of discovered cards will be printed
at startup by libajantv2, and then voctocore will quit.

Optional attributes of AJA sources are:

| Attribute Name | Example Values | Default | Description (follow link)
| ------------------ | ------------------------------------------------------- | ---------- | -----------------------------------------
| `device` | `00A00012`, ... | `` | [AJA `device-identifier`](https://gstreamer.freedesktop.org/documentation/aja/ajasrc.html#ajasrc:device-identifier)
| `channel` | `0`, `1`, `2`, ... | `0` | [AJA `channel`](https://gstreamer.freedesktop.org/documentation/aja/ajasrc.html#ajasrc:channel)
| `video_mode` | `auto`, `1080p-2500`, `1080p-6000-a`, `1080i-5000`, ... | `auto` | [AJA `video-format`](https://gstreamer.freedesktop.org/documentation/aja/ajasrc.html#GstAjaVideoFormat)
| `audio_source` | `embedded`, `aes`, `analog`, `hdmi`, `mic` | `embedded` | [AJA `audio-source`](https://gstreamer.freedesktop.org/documentation/aja/ajasrc.html#GstAjaAudioSource)

##### 1.6.1.1.8. Common Source Attributes

These attributes can be set for all *kinds* of sources:

Expand Down Expand Up @@ -608,11 +649,11 @@ enabled=true
| ------------------ | ----------------------------------- | ----------- | -----------------------------------------
| `enable` | `true` | false |

#### 1.6.2.8 SRT Server
#### 1.6.2.8. SRT Server

`srtserver` not yet implemented for 2.0

#### 1.6.2.9 Program Output
#### 1.6.2.9. Program Output

Outputs the current **Mix Recording** (aka **Mix** in GUI) to a defined gstreamer sink

Expand All @@ -623,6 +664,20 @@ videosink = autovideosink # this is the default
audiosink = autoaudiosink # this is the default
```

##### 1.6.2.9.1. AJA Program Output

You can, for instance, output the Mix recording to an AJA card (including one
from which you are capturing):

```ini
[programoutput]
enabled = true
# channel=4 --> SDI 5
videosink=queue max-size-bytes=0 max-size-buffers=0 max-size-time=1000000000 ! videoconvert ! video/x-raw,width=1920,height=1080,framerate=60/1 ! ajaout.video ajasinkcombiner name=ajaout ! ajasink channel=4 reference-source=input-1
# Note that the audio buffers must be padded to 16 channels and chunked at the framerate or the pipeline will stall or sound very odd
audiosink=audiomixmatrix in-channels=2 out-channels=16 channel-mask=-1 mode=first-channels ! audiobuffersplit output-buffer-duration=1/60 ! ajaout.audio
```

### 1.6.3. A/V Processing Elements

#### 1.6.3.1. DeMux
Expand Down
3 changes: 3 additions & 0 deletions voctocore/lib/sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
def spawn_source(name, port, has_audio=True, has_video=True):

from lib.config import Config
from lib.sources.ajaavsource import AJAAVSource
from lib.sources.decklinkavsource import DeckLinkAVSource
from lib.sources.imgvsource import ImgVSource
from lib.sources.tcpavsource import TCPAVSource
Expand All @@ -24,6 +25,8 @@ def spawn_source(name, port, has_audio=True, has_video=True):
sources[name] = ImgVSource(name)
elif kind == 'decklink':
sources[name] = DeckLinkAVSource(name, has_audio, has_video)
elif kind == 'aja':
sources[name] = AJAAVSource(name, has_audio, has_video)
elif kind == 'file':
sources[name] = FileSource(name, has_audio, has_video)
elif kind == 'tcp':
Expand Down
99 changes: 99 additions & 0 deletions voctocore/lib/sources/ajaavsource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
import logging
import re

from lib.config import Config
from lib.sources.avsource import AVSource


class AJAAVSource(AVSource):

timer_resolution = 0.5

def __init__(self, name, has_audio=True, has_video=True):
super().__init__('AJAAVSource', name, has_audio, has_video, show_no_signal=True)

self.device = Config.getAJADeviceIdentifier(name)
self.input_channel = Config.getAJAInputChannel(name)
self.vmode = Config.getAJAVideoMode(name)
self.asrc = Config.getAJAAudioSource(name)
self.name = name

self.signalPad = None
self.build_pipeline()

def port(self):
return "AJA #{}".format(self.device)

def attach(self, pipeline):
super().attach(pipeline)
self.signalPad = pipeline.get_by_name(
f'ajasrc-{self.name}')

def num_connections(self):
return 1 if self.signalPad and self.signalPad.get_property('signal') else 0

def get_valid_channel_numbers(self):
return (2, 8, 16)

def __str__(self):
return f'AJAAVSource[{self.name}] reading card #{self.device}'

def build_source(self):
pipe = f"""
ajasrc
name=ajasrc-{self.name}
device-identifier={self.device}
channel={self.input_channel}
video-format={self.vmode}
! ajasrcdemux
name=ajasrcdemux-{self.name}
"""

# add rest of the video pipeline
if self.has_video:
pipe += f"""\
ajasrcdemux-{self.name}.video
"""

# maybe add deinterlacer
if deinterlacer := self.build_deinterlacer():
pipe += f"""\
! {deinterlacer}
"""

pipe += f"""\
! videoconvert
! videoscale
! videorate
name=vout-{self.name}
"""

if chans := self.internal_audio_channels():
pipe += f"""\
ajasrcdemux-{self.name}.audio
! audioconvert
"""

if chans < 16:
# Take the first {chans} channels.
pipe += f"""\
! audiomixmatrix
in-channels=16
out-channels={chans}
mode=first-channels
"""
pipe += f"""\
name=aout-{self.name}
"""

return pipe

def build_audioport(self):
return f'aout-{self.name}.'

def build_videoport(self):
return f'vout-{self.name}.'

def get_nosignal_text(self):
return f"{super().get_nosignal_text()}/AJA{self.device}"

0 comments on commit 5c036b0

Please sign in to comment.