Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for AJA capture cards #353

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}"