AndroidHDMI for Channels (ah4c): A virtual channel tuner using HDMI Encoder(s) + streaming stick(s)

The tuning script is called for each of the four tuners on the card this way:

If I send the tuning command from bmitune.sh and leave the CMD field empty would that mess how ah4c is able to switch from one tuner to the next?

Here is a sample of the m3u that bmitune.sh is calling to. There is one of each tuning source (Directv, YoutubeTV, and Hbo Max app):

#EXTM3U

#EXTINF:-1 channel-id="202" channel-number="202" tvc-guide-stationid="58646" tvg-group="" tvg-logo="",CNN HD
http://{{ .IPADDRESS }}/play/tuner/dtvCNNHD~d3603aea-f5d8-e789-786c-43c5e8799428
#EXTINF:-1 channel-id="#277" channel-number="277" tvc-guide-stationid="11871" tvg-group="" tvg-logo="",ACC
#http://{{ .IPADDRESS }}/play/tuner/ytv277~YaqNgmedD7U?onboard=1
#EXTINF:-1 channel-id="818" channel-number="818" tvc-guide-stationid="" tvg-group="" tvg-logo="",HBOCOM
http://{{ .IPADDRESS }}/play/tuner/max818

Like is said, I had to revert to the contentID method for the max app because either the length of the deep link or the "/" that appears in it does not allow it to be read from the m3u file.

Apologies, but I don't recognize this UI. Where is this from?

But I get the idea -- these are the CMD1, CMD2 environment variables. Let me ponder this briefly, and I'll get back to you.

The picture is from is the "Edit ENV Configuration & tuners" page from the ah4c web interface.

I am using the non docker version.

The only way I can see out is to have a clunky script for each provider in the same way I have a TuneMaxApp.sh for hbo that takes care of the welcome screen.

I don't think that'll be necessary. I re-wrote your bmitune.sh script to clean it up, and add a new concept or two. It writes the desired sleep time to a file, with directories named by tuner IP.

I also re-worked your M3U, using another tilde(~) to better handle the data. Also, I URL encoded the slash(/) in your Max contentID value (using a %2F), which should allow it to be passed into the script. Then I URL decoded it to send via ADB.

bmitune.sh:

#!/bin/bash

provider=$(echo $1 | awk -F~ '{print $1}')
callSign=$(echo $1 | awk -F~ '{print $2}')
contentID=$(echo $1 | awk -F~ '{print $3}')
tunerIP="$2"
tunerNoPort="${tunerIP%%:*}"
    mkdir -p $tunerNoPort
adbTarget="adb -s $tunerIP shell am start -a android.intent.action.VIEW -d  "

# Adjust the desired sleep times by provider
declare -A sleepTime=(
  [dtv]=15
  [max]=15
  [yttv]=15
)

echo "${sleepTime[$provider]}" > $tunerNoPort/tune_sleep 

case "$provider" in
    "dtv")
        $adbTarget dtvnow://deeplink.directvnow.com/play/channel/$callSign/$contentID
        ;;
    "max")
        $adbTarget https://play.max.com/channel/watch/$(printf '%b\n' "${contentID//%/\\x}")
        ;;
    "yttv")
        $adbTarget https://tv.youtube.com/watch/$contentID?onboard=1 -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity
        ;;
    *)
        exit 1
        ;;
esac

magewell.m3u:

#EXTM3U

#EXTINF:-1 channel-id="202" channel-number="202" tvc-guide-stationid="58646" tvg-group="" tvg-logo="",CNN HD
http://{{ .IPADDRESS }}/play/tuner/dtv~CNNHD~d3603aea-f5d8-e789-786c-43c5e8799428

#EXTINF:-1 channel-id="277" channel-number="277" tvc-guide-stationid="11871" tvg-group="" tvg-logo="",ACC
http://{{ .IPADDRESS }}/play/tuner/yttv~~YaqNgmedD7U

#EXTINF:-1 channel-id="818" channel-number="818" tvc-guide-stationid="" tvg-group="" tvg-logo="",HBOCOM
http://{{ .IPADDRESS }}/play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694%2F452e5d78-1cca-59a9-8508-21b25d813874

Then, rather than using an input1.sh script, I would use this directly in the CMD field (adjust by tuner #):

{ sleep $(cat 192.168.1.130/tune_sleep); bash -c "magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m "; }

This all needs to be tested of course!

Will try this out when I wake up later today and report back.

I get this for each channel on the three channels.

[START] ah4c is ready
[GIN-debug] Listening and serving HTTP on :7654
[GIN-debug] Request: 127.0.0.1 POST /configsave, latency: 158.507µs, status: 301
[GIN-debug] Request: 127.0.0.1 GET /config, latency: 1.040241ms, status: 200
Attempting network tune for device  192.168.1.130:5555 yttv~~YaqNgmedD7U 
[ERR] Failed to fetch source: Get "": unsupported protocol scheme ""
Attempting network tune for device  192.168.1.131:5555 yttv~~YaqNgmedD7U 
[ERR] Failed to fetch source: Get "": unsupported protocol scheme ""
[ERR] Failed to tune device(s) not available
[GIN-debug] Request: 192.168.1.188 GET /play/tuner/yttv~~YaqNgmedD7U, latency: 108.19µs, status: 500
Attempting network tune for device  192.168.1.130:5555 yttv~~YaqNgmedD7U 
[ERR] Failed to fetch source: Get "": unsupported protocol scheme ""
Attempting network tune for device  192.168.1.131:5555 yttv~~YaqNgmedD7U 
[ERR] Failed to fetch source: Get "": unsupported protocol scheme ""
[ERR] Failed to tune device(s) not available
[GIN-debug] Request: 192.168.1.188 GET /play/tuner/yttv~~YaqNgmedD7U, latency: 176.372µs, status: 500
[GIN-debug] Request: 192.168.1.188 GET /play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694%2F452e5d78-1cca-59a9-8508-21b25d813874, latency: 430ns, status: 404
[GIN-debug] Request: 192.168.1.188 GET /play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694%2F452e5d78-1cca-59a9-8508-21b25d813874, latency: 992ns, status: 404

Narrowed the issue down to the command for tuning.

{ sleep $(cat 192.168.1.130/tune_sleep); bash -c "magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m "; }

ah4c does not like this. It spits out this when I try to load it with that command in place:

christophe@christophe:~/ah4c$ ./androidhdmi-for-channels 
[START] ah4c is starting
[ENV] Loading env
[ENV] IPADDRESS                  
[ENV] ALERT_SMTP_SERVER          
[ENV] ALERT_AUTH_SERVER          
[ENV] ALERT_EMAIL_FROM           
[ENV] ALERT_EMAIL_PASS           
[ENV] ALERT_EMAIL_TO             
[ENV] ALERT_WEBHOOK_URL          
[ENV] ALLOW_DEBUG_VIDEO_PREVIEW  
panic: Could not find an environment variable named NUMBER_TUNERS

goroutine 1 [running]:
main.loadenv()
	/home/christophe/ah4c/main.go:967 +0xd0f
main.main()
	/home/christophe/ah4c/main.go:1008 +0x2b

I have resorted to simply this for debugging:

magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m

So far dtv and yttv works but hbo is still not picked up. I still get a "status : 404" when it tries to read the m3u:

[GIN-debug] Request: 192.168.1.188 GET /play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694%2F452e5d78-1cca-59a9-8508-21b25d813874, latency: 594ns, status: 404
[GIN-debug] Request: 192.168.1.188 GET /play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694%2F452e5d78-1cca-59a9-8508-21b25d813874, latency: 982ns, status: 404

I'll play around with putting multiple shell commands into CMD when I have a chance, but in the meantime putting them in a script is fine too.

The good news here is that the full content ID value is being passed, but the URL encoding/decoding isn't working.

I think it's safe to say that these are actually two values, given the presence of the slash (aka a separator), much like DTV requires two values.

So I'd propose replacing that slash with another tilde, and adjusting the script as follows:

#!/bin/bash

provider=$(echo $1 | awk -F~ '{print $1}')
callSign=$(echo $1 | awk -F~ '{print $2}')
contentID=$(echo $1 | awk -F~ '{print $3}')
contentID2=$(echo $1 | awk -F~ '{print $4}')
tunerIP="$2"
tunerNoPort="${tunerIP%%:*}"
    mkdir -p $tunerNoPort
adbTarget="adb -s $tunerIP shell am start -a android.intent.action.VIEW -d  "

# Adjust the desired sleep times by provider
declare -A sleepTime=(
  [dtv]=15
  [max]=15
  [yttv]=15
)

echo "${sleepTime[$provider]}" > $tunerNoPort/tune_sleep 

case "$provider" in
    "dtv")
        $adbTarget dtvnow://deeplink.directvnow.com/play/channel/$callSign/$contentID
        ;;
    "max")
        $adbTarget https://play.max.com/channel/watch/$contentID/$contentID2
        ;;
    "yttv")
        $adbTarget https://tv.youtube.com/watch/$contentID?onboard=1 -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity
        ;;
    *)
        exit 1
        ;;
esac

With the M3U adjusted to remove the URL encoded slash, and using a tilde instead:

#EXTM3U

#EXTINF:-1 channel-id="202" channel-number="202" tvc-guide-stationid="58646" tvg-group="" tvg-logo="",CNN HD
http://{{ .IPADDRESS }}/play/tuner/dtv~CNNHD~d3603aea-f5d8-e789-786c-43c5e8799428

#EXTINF:-1 channel-id="277" channel-number="277" tvc-guide-stationid="11871" tvg-group="" tvg-logo="",ACC
http://{{ .IPADDRESS }}/play/tuner/yttv~~YaqNgmedD7U

#EXTINF:-1 channel-id="818" channel-number="818" tvc-guide-stationid="" tvg-group="" tvg-logo="",HBOCOM
http://{{ .IPADDRESS }}/play/tuner/max~~a903ca8a-6d5e-559b-a027-f82997397694~452e5d78-1cca-59a9-8508-21b25d813874

In addition to the above bmitune.sh and M3U changes, try this approach in the CMD field:

"bash -c 'sleep $(cat 192.168.1.130/tune_sleep); magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m '"

It's working with the command and with the adjustment to the m3u for hbo.

I had to put the declared times between quotation marks before they registered. The tune_sleep file was coming up empty until I did that.

I am testing with vlc and I notice that the delay doesnt always work after a fresh restart of ah4c. Testing a bit more for consistency.

#!/bin/bash

provider=$(echo $1 | awk -F~ '{print $1}')
callSign=$(echo $1 | awk -F~ '{print $2}')
contentID=$(echo $1 | awk -F~ '{print $3}')
contentID2=$(echo $1 | awk -F~ '{print $4}')
tunerIP="$2"
tunerNoPort="${tunerIP%%:*}"
    mkdir -p $tunerNoPort
adbTarget="adb -s $tunerIP shell am start -a android.intent.action.VIEW -d  "

# Adjust the desired sleep times by provider
declare -A sleepTime=(
  [dtv]="8"
  [max]="15"
  [yttv]="5"
)

echo "${sleepTime[$provider]}" > $tunerNoPort/tune_sleep 

case "$provider" in
    "dtv")
        $adbTarget dtvnow://deeplink.directvnow.com/play/channel/$callSign/$contentID
        ;;
    "max")
        $adbTarget https://play.max.com/channel/watch/$contentID/$contentID2 && sh maxhello.sh $tunerIP
        ;;
    "yttv")
        $adbTarget https://tv.youtube.com/watch/$contentID?onboard=1 -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity
        ;;
    *)
        exit 1
        ;;
esac

Cool. What are you doing in maxhello.sh?

maxhello gets rid of the profile screen for the hbo app.

#!/bin/bash

adb -s $1 shell input keyevent 19
adb -s $1 shell input keyevent 19
adb -s $1 shell input keyevent 19
adb -s $1 shell input keyevent 19
adb -s $1 shell input keyevent 19
adb -s $1 shell input keyevent 19
sleep 6
adb -s $1 shell input keyevent 62
adb -s $1 shell input keyevent 66

Basically lots of spamming then pressing the select key.

@bnhf

New question. Is there a way to serve each of my tuners individually over a network instead of all in one m3u? Say for example I wanted to share each magewell tuner from ah4c into adbtuner?

Can you give me a bit more detail on what you have in mind here? And, maybe an example or two?

So if you recall from above I am using a magewell card that encodes four HDMI streams with the command for each stream looking this:

magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m

The number for the -i input increases by one for each tuner.

I am trying to find a way to get the tuners working in adbtuner as well and have been thinking of ways to share them form ah4c since adbtuner only works with network encoders like link pi and such.

The problem is that ah4c sends them all out in a m3u file and does not provide a path to each individual tuner. I am wondering if there might to be a path to each tuner within ah4c that I am missing.

I can share one tuner at a time using the following:

magewell2ts -i 1 -s 100 -q 12 -c h264_qsv -m | ffmpeg -i - -c:v copy -c:a copy -f mpegts  rtp://127.0.0.1:1234

or this one:

magewell2ts -i 2 -s 100 -q 12 -c h264_qsv -m | ffmpeg -i - -c:v copy -c:a copy -f mpegts  udp://127.0.0.1:1234/1

Is there a way this can be done using ah4c?

I guess in a sense I am trying to turn the linux box with the tuner into a link pi of sorts.

This may work, with a slight variation in approach. If you thought of your Linux box plus the Magewell capture card as a virtual encoder (e.g. LinkPi), and let ADBTuner handle the tuning as usual.

Untested, but try this for your encoder URL in ADBTuner:

http://<linux box>:7654/play/tuner<tuner number>/magewell~tuner<tuner number>

And in your prebmitune.sh, bmitune.sh and stopbmitune.sh you need to set a provider name of "magewell" to do nothing, as ADBTuner would be doing the virtual channel changes.

ah4c should do its thing as usual (using the specified tuner number) as far as starting the stream using the Magewell card and ffmpeg -- just without sending any ADB commands to the streaming stick.

EDIT: Corrected URL

Thanks!

This format works with my testing so far with vlc"

http://<linux box>:7654/play/tuner<tuner number>/magewell~tuner<tuner number>

The content of prebmitune.sh, bmitune.sh, and stopbmitune.sh is kept at simply:

#!/bin/bash

This should get all the tuners that work with ah4c to work with adbtuner. I need to spin up a docker of adbtuner to test.

1 Like

@bnhf

Sorry to bother you. I want to try and teach myself some stuff.
I am going to try experimenting some.
I would like to try some vibe coding with VSCode and Cline on the ah4c files specifically for the ATV version (bnhf/ah4c:appletv).

My first question is which git would you use as a base?
bnhf/ah4c or
sullrich/ah4c

Both have the "Dockerfile-pyatv" file and it looks like sullrich ah4c has been updated more recently with at least some of the updates by you.

If I what to experiement locally and I understand all this right I would ...

  1. Clone the git repository
  2. Make changes to files
  3. Run Docker Build using "Dockerfile-pyatv" for the appletv build

Don't know golang at all and only how to spin up docker containers in portainer so it should be a great adventure. :joy:

This one.

Correct on all counts.

Absolutely -- should be fun!

1 Like

@boukmandutty, I am planning something for my own Magewell card that should help. That is to write a thin web wrapper for magewell2ts that will make each port look like a networked tuner much like a URayTech or LinkPi.

Keep me posted. Will be willing to test along with you. For now feeding it to adbtuner from ah4c seems quite steady, but only with the "Keep Device Awake" option enabled. If that is not enabled then the tuner falls asleep and recordings are missed.