Az elmúlt napok szinte meggyőztek arról, hogy a foci küzdősport. Vagy legalábbis az ahhoz vezető út, hogy legálisan nézhessem, biztosan.
Ez egy folytatás. Az előző ide kapcsolódó írásom nélkül valószínűleg még kevésbé lesz érthető a tartalom. De röviden a lényeg. A szebbik felem és én is szeretjük követni a helyi fociklub mérkőzéseit. Ha épp nem a stadionban, akkor TV-ben. Viszont külföldön nincs M4, ami közvetíti a legtöbb fontos sporteseményt "ingyen", hanem az ember rá van kárhoztatva, hogy havi ~11000 forintot rááldozzon minimum az online/műholdas TV-re, hogy meg tudja nézni azt a havi két/három meccset, ami éppen érdekli.
Ezzel nincsen gond, hiszen a stadion még drágább lenne. Azzal már inkább, hogy az egyetlen szolgáltató, aki ezeket az adásokat sugározza, nagyon kevés eszközt támogat. Amíg LG TV-m volt, nem volt gond. Amióta Sony-m van Android TV-vel, minden megy, csak pont a foci nem.
Én meg istenfélő ember vagyok, véletlenül se akarnám magamra haragítani az asszonyt, ezért kellett egy megoldás a problémára mindenképp. Erről írtam korábban.
Odáig eljutottunk, hogy egy olcsó Raspberry Pi0 a TV-re kötve igény esetén átalakítja a netes streamet megfelelő formátumba, amit már kezel az Android TV is. A cikkben részletesen kifejtettem, hogy mennyire macera volt megfejteni a Microsoft féle Smooth Streaming (MSS) megoldást, amit a TV szolgáltató biztosít, illetve ezt működő és jóval támogatottabb MPEG-DASH (mpd) formába önteni. Illetve feldolgozni az egyes bejövő média szeleteket, hogy azt a TV/Kodi is megegye. A cikkben kifejtettem, hogyan jutottam el a manifest és a videó kódolásáig, az audiót meg teljesen kihagytam, mert a videó alapján szinte gyerekjáték volt implementálni.
Egy nagyon alap Móriczka ábra a jelenlegi állapotról
Az idők alatt szinte el is felejtettem, hogy a projekt létezik, olyan jól működött. Egészen pár nappal ezelőttig. Ugyanis nagyba néztük volna a meccset, erre nem indult... Hát mondom fantasztikus, mi mehetett félre? Első blikkre a lejátszónak a hanggal volt baja, szóval azt kiszedtem. Ugyanis ahogy a régi cikkben említettem, a szolgáltató külön streameli a hangot és a videót és azt is mindenféle eltérő minőségben, hogy lassú internet esetén is élvezhető legyen a tartalom mondjuk 720p-ben. Így gond nélkül működött.
Kiderült, hogy a korábban használt AAC-LC kodeket a szolgáltató lecserélte EAC-3-ra, szóval elvileg Dolby Digital Plus-ra. Ez tök jó hír, mert jóval nagyobb bitrátával, tök jó minőségben jön így a hang. Igen ám, csak erre nem volt a szoftver felkészülve.
És itt következett az élet egyik nagy dilemmája:
Megéri-e ez nekem ismét a tömérdek szenvedést, illetve kutatást, vagy hagyjam a fenébe és fizessek egyszer 50 eurót a szolgáltató saját set top boxáért és felejtsem el a gondokat remélhetőleg örökre. Nos, akinek eddig nem világos, nyilván nem lennék önazonos, ha fizetnék. De csapjunk is bele!
Előzmények
Ahhoz, hogy megértsük a problémát, kicsit vissza kell menni az időben arra az állapotra, ahol még csak videóm volt, az előző cikk alapján. Ha emlékeztek kifejtettem, hogy a szolgáltató ad egy smooth streaming technológián alapuló XML fájlt, amit időnként le kell kérni, és ez fogja megmondani a lejátszónak többek közt, hogy honnan tudja majd letölteni a tényleges élő média feldarabolt szeleteit. Namost ez a régi AAC-LC hang esetében így nézett ki:
Abba nem megyek bele ismét, hogy melyik StreamIndex mező mit jelent és hogy hogy lehet oda eljutni, hogy letöltsük az egyes média szeleteket, ahogy azok létrejönnek a távoli szerveren, hiszen ezt már korábban részleteztem. Itt sincs ez máshogy. Ami viszont a Smooth Streaming esetében érdekes, hogy a szeletek nem tartalmazzák a meta adatokat (felbontás, kodek, csatornák száma stb), ami alapján a lejátszó elő tudná készíteni a lejátszást. A legegyszerűbb megoldás erre az, ha van egy üres init.mp4 fájl, ami csak a metaadatokat tartalmazza, ezt a lejátszó betölti az élő adás lejátszása előtt és boldogság van. A népszerű MPEG-DASH esetén pl. ez pontosan így van megoldva. A szerver generál egy init.mp4 szegmenst, ezt a kliensek letöltik és használják boldogan. Smooth streaming esetében sincs ez máshogy, kivéve, hogy itt nem tölt le a manifesten kívül semmilyen init szegmenst a kliens. A manifestből halászik össze elegendő adatot ahhoz, hogy össze tudjon rakni egy használható init szegmenst. Szerintem ez volt az egyik ok, hogy a smooth streaming szinte teljesen kihalt.
Kicsit szerintem szürreális elvárni, hogy minden kliensnek ismernie kell legalább az mp4 formátumra jellemző box névre hallgató építőkövek egy részét, hogy implementálni tudja ezt az amúgy is szegényesen dokumentált szabványt. Amikor a szerver eleve ismeri az mp4-et, hiszen elő kell valahogy állítania. Gondolom csak azért terjedt el, mert a Microsoft egy időben nagyon szorgalmazta a Silverlightot a Windowsban, aztán el is kaszálta.
Nade, megint sok a rizsa. Szóval ott tartottunk, hogy a manifestből mindent ki tudunk deríteni, ami kell. A feladatunk: összerakni egy audió init szegmenst ezek alapján. Mit szólnátok, ha azt mondanám, hogy a fentebbi kép tartalmaz minden adatot ahhoz, hogy ezt előállítsuk?
A legtöbb mező egészen egyértelmű. Azt a FourCC alapján látjuk, hogy AAC-LC, azaz AAC low complexity profillal lesz dolgunk. A bitráta is remélhetőleg egyértelmű, bár ez inkább referencia bitráta, láttam már több szervert, ami teljesen más módon határozta meg. A hangcsatornák száma 2 lesz. A mintavételezési ráta pedig egyike a standard értékeknek. Maradt három érték, a BItsPerSample egész beszédes nevű, szóval volt egy tippem, hogy mire lesz jó pontosan. A PacketSize viszont pl elsőre kínai volt. Felültöttem egy ezer éves Microsoft doksit, ami ennyit írt róla:
PacketSize: [Required] Specifies the block alignment, in bytes.
Ezzel mondjuk nem voltam nagyon kisegítve, de előkaptam egy másik doksit, ami a hivatalos smooth streaming docs. Itt egy direkt link a PDF-hez:
PacketSize (variable): The size of each audio packet, in bytes.
Na ez már érthetőbbnek tűnik. Viszont sehogy se sikerült megfejtenem, hogy a képen pl 4 a packet size, ez azt jelentené, hogy 4 byte egy audió csomag. Namost, ha ez alatt a szegmenst érti, amit letölt, az sosem 4 byte, sőt, annyiból lehetetlen összerakni az audiót. Viszont ha egy minta méretét érti 4 byte alatt, az meg egyszerűen nem adja ki matekkal. Na mindegy. Szerencsére ez a mező nem fog kelleni.
CodecPrivateData
Na igen... Ez a szörnyűség minden rémálmomnak és ezen posztnak is központi eleme lesz. Bevallom, elsőre túl nagy falatnak tűnt és én csak focit akartam nézni hanggal, lehetőleg még aznap este. Fogtam tehát a videós részből korábban megtanult box összerakós mutatványt, majd AAC-re szabtam pár értéket:
init := mp4.NewMP4Init()
init.AddChild(mp4.NewFtyp("dash", 0, []string{"iso6", "piff", "mp4a"}))
moov := mp4.NewMoovBox()
init.AddChild(moov)
moov.AddChild(mp4.CreateMvhd())
moov.AddChild(mp4.NewMvexBox())
init.AddEmptyTrack(timeScale, "audio", lang)
psshBox := mp4.PsshBox{
SystemID: PlayreadySystemID,
Data: playreadyBlobBytes,
}
Aztán fogtam az így frissen létrejött [trak] boxot (trak := init.Moov.Trak
) és hozzácsaptam egy AAC leírót:
err = trak.SetAACDescriptor(aac.AAClc, 48000)
Érdemes amúgy átolvasni ennek a függvénynek a forráskódját, mert érdekes, hogy milyen egyszerű az egész a zord külső mögött és ötletet adhat más egyéb AAC alapú profilok implementálásához is a jövőben. De mi ezzel a gond? Nos, elegánsan beégettem az objektum típust AAClc-nek, szóval ha a szolgáltató időközben vált más AAC profilra, akkor eleve bukta a dolog. Illetve mintavételezésnél is beégettem a 48000-at, ami egészen addig OK, amíg nem jön egy olyan csatorna, ahol más a mintavételezés, aztán valószínűleg a lejátszó le fogja játszani a hangot, de teljesen más tempóban. Aztán azon kapom magam, hogy az Alvin és a mókusok szinkronjait eredeti mély hangjukon nézem.
Hogy megintcsak őszinte legyek, nem foglalkoztam ezzel azóta. Beégettem, jóvanazúgy. Szerencsére nem volt vele gond. Aztán most, hogy a csatorna nem csak AAC kodeket használ már, kénytelen voltam megint hozzányúlni és eljutni a jó megoldásig...
Szóval CodecPrivateData... Nézzük, mit ír róla hang esetében a korábbi link:
CodecPrivateData: [Required] Specifies the extra codec-specific information appended to the end of the WAVEFORMATEX structure. This value is base16 encoded.
Ok, ez egész konkrétnak tűnik. Nézzük, mit ír a doksi róla. Viszont a link el van törve, de szerencsére valahogy mégis odamutat a tényleges tartalomra:
Namost, ez marha izgi, csak azt nem látom, hogy az a kodek specifikus infó, amit ide akar tűzni, az pontosan hova is jönne. Feltételezem, hogy az a struktúra lemaradt a doksiból, hogy azt pontosan hogy tárolja. Viszont a cbSize legalább beszédes doksival rendelkezik:
cbSize
Size, in bytes, of extra format information appended to the end of the WAVEFORMATEX structure. This information can be used by non-PCM formats to store extra attributes for the wFormatTag. If no extra information is required by the wFormatTag, this member must be set to 0. For WAVE_FORMAT_PCM formats (and only WAVE_FORMAT_PCM formats), this member is ignored. When this structure is included in a WAVEFORMATEXTENSIBLE structure, this value must be at least 22.
Remek. Legalább jó úton vagyunk. A CodecPrivateData ezek szerint ez az egyedi, kodek specifikus adat lesz. Ok, de itt vége a doksinak. Honnan tudom meg, hogy AAC-LC esetében mi a helyes? Borzasztóak az MS doksik.
Itt most egy hosszas nyomozás következett. Ne kérdezzétek, hogy hogy, mert én sem tudom felidézni és újból megtalálni sem ezt a doksit, viszont a böngésző előzményekből meglett a válasz: [link].
A-ha! Ez egészen fantasztikus. Végre valami! Szóval az elképzelés az, hogy ez a bizonyos pbAudioSpecificConfig[1]
lesz a megoldás az összes eddigi fejfájásomra. De az meg mi? Nézzük, mit írnak róla lentebb:
pbAudioSpecificConfig[1]
A byte array that contains the value of AudioSpecificConfig(), as defined by ISO/IEC 14496-3. The array might be larger than the size given in the structure declaration. Use the value of wfInfo.wfx.cbSize to determine the size.
Csodálatos. Ezt a cbSize-t, amiről szűkszavúan regél drága barátunk, nemrég veséztük ki. Összeért a kör. Egy probléma van csak. Az a szabvány, amit a doksi említ, piszok drága. 221 svájci frankot kérnek érte, ami a cikk írásának pillanatában kicsit több, mint 94000 magyar forintnak felel meg. Ha ezt az utat választom, kb a szolgáltatói set top box rendelése is jobban megéri. Sajnos ezek a szabványok mind mocsok drágák. Eddig lenéztem őket, de amúgy egész használhatóan leírják az egyes implementációhoz szükséges részleteket, szóval azt legalább értem, hogy miért erre hivatkoznak. A neten van egy pár kétes forrásból leakelt verzió, de ezek olvasására senkit nem bíztatok a lapcsalád anti-warez szabálya miatt (meg simán lehet outdated az infó).
De mit tehet itt egy ló, ha nem akar fizetni? Nem tudom, én csak követtem az intuícióim. Ha ISO és mp4, akkor nekem nagyon bevált az mp4ff forráskódjának olvasgatása. Fantasztikus lib, nagyon nagy hangsúlyt fektetve arra, hogy a sok szabványból egy tényleges implementációt hozzon. A videós dolgokhoz is szinte csak ezt a libet használom a mai napig. A kedvenc golang projektem lett. És legnagyobb meglepetésemre, konkrétan van egy megoldása a korábbi mezőt dekódolni.
Nincs más dolgunk tehát, mint fogni a képről a 1120-at, átváltani hexából bájtokra, aztán be kell adni a függvénynek és imátkozni, hogy kiad valami értelmeset. Nézzük hát az alábbi kódot:
package main
import (
"bytes"
"encoding/hex"
"log"
"github.com/Eyevinn/mp4ff/aac"
)
func main() {
codecPrivateDataHex := "1120"
codecPrivateData, _ := hex.DecodeString(codecPrivateDataHex)
audioSpecificConfig, err := aac.DecodeAudioSpecificConfig(bytes.NewReader(codecPrivateData))
if err != nil {
log.Fatalf("Failed to decode audio specific config: %v", err)
}
log.Printf("AudioSpecificConfig: %+v", audioSpecificConfig)
}
Lefuttatva pedig:
[steve@todo test]$ go run main.go
2025/02/13 21:07:49 AudioSpecificConfig: &{ObjectType:2 ChannelConfiguration:4 SamplingFrequency:64000 ExtensionFrequency:0 SBRPresentFlag:false PSPresentFlag:false}
Azt mondtam az AI-nak, hogy szimmetrikus és szép alagutat csináljon...
Ahh, de csodás a fény az alagút végén! Ez bizony mindent is felismert 2 bájtnyi csomagolt adatból. És a legszebb az egészben, hogy az implementálással se kellett szenvedni, hogy pontosan hány bitet hogyan packelek ki az adatból, mindent megold a library. Királyság.
Igen ám, csak ezzel pont nem vagyunk kisegítve. Kicsit előrébb vagyunk ugyan, hiszen AAC-vel nem lesz gond, de nekünk EAC-3 kell...
EAC-3
Ha már kénytelenek vagyunk ezt is implementálni, lessük meg, hogy mivel van dolgunk pontosan:
Szerencsére minden mező ismerős a korábbiak alapján. Rögtön elő is kerestem, hogy mi kell ahhoz, hogy létre tudjunk hozni egy megfelelő init szegmenst az mp4ff segítségével:
// SetEC3Descriptor - Modify a TrakBox by adding EC-3 SampleDescriptor
func (t *TrakBox) SetEC3Descriptor(dec3 *Dec3Box) error {
stsd := t.Mdia.Minf.Stbl.Stsd
nrChannels, _ := dec3.ChannelInfo()
fscod := dec3.EC3Subs[0].FSCod
samplingFrequency := AC3SampleRates[fscod]
ec3 := CreateAudioSampleEntryBox("ec-3",
uint16(nrChannels), // Not to be used, but we set it anyway
16, uint16(samplingFrequency), dec3)
stsd.AddChild(ec3)
return nil
}
Tehát nekünk egy Dec3 box kéne... Kinéztem gyorsan egy tesztesetet:
Dec3Box{DataRate: 448,
NumIndSub: 0,
EC3Subs: []EC3Sub{
{FSCod: 2}},
Reserved: []byte{}}
Aztán ezt összerakva kb ezzel álltam elő:
init := mp4.NewMP4Init()
init.AddChild(mp4.NewFtyp("dash", 0, []string{"iso6", "piff"}))
moov := mp4.NewMoovBox()
init.AddChild(moov)
moov.AddChild(mp4.CreateMvhd())
moov.AddChild(mp4.NewMvexBox())
init.AddEmptyTrack(timeScale, "audio", lang)
psshBox := mp4.PsshBox{
SystemID: PlayreadySystemID,
Data: playreadyBlobBytes,
}
dec3Box := mp4.Dec3Box{DataRate: 448,
NumIndSub: 0,
EC3Subs: []EC3Sub{
{FSCod: 2}},
Reserved: []byte{}}
err = trak.SetEC3Descriptor(dec3Box)
És a vicc az, hogy az eredményt lejátszotta a TV-m. Igaz, hogy kb 10 másodpercig a stream indítását követően nincs hang egyáltalán (feltételezem ilyenkor próbálja korrigálni a hang meta adatait a lejátszó a bejövő stream alapján), nem akartam így hagyni, mert ez így nagyon csúnya. Meg a DRM miatt nem tudom letölteni a hangsávot és megnézni spektrum analizátorral, hogy egyáltalán jó-e a felismert metaadat és nem-e torzít a hang. Abban se voltam biztos, hogy nem-e lesz-e egy olyan lejátszó, ami nem támogatja majd a rossz értékeket helyesen.
Elkezdtem olvasgatni, hogy mit jelenthetnek ezek az értékek, mint FSCod, NumIndSub stb. Mire nagyjából megértettem minden mezőt, arra jutottam, hogy nem elég a manifestben található adat ahhoz, hogy én ezt összerakjam. Hacsak nem a CodecPrivateData kell már megint... Nyilván az kell, de hogy értelmezem a 000603000000AF87FBA7022DFB42A4D405CD93843BDD0600200400
hexa értéket...
Elkezdtem keresgélni a Githubon, hogy mégis mi lehet ez, és viszonylag hamar belebotlottam a yt-dlp megoldásába. Ők egész egyszerűen fogják és bepakolnak egy statikus AAC-nak megfelelő boxot. Ennyivel le is van tudva az egész... Namost, nekem ennél már most jobb a megoldásom, mert az legalább valid ec3 box, csak nem a jó paraméterekkel.
Ezek után találtam egy másik érdekességet, mégpedig egy ExoPlayer github hibajegyet. Nagy reményeim voltak, mert az ExoPlayer egy komoly és népszerű Androidra írt lejátszó, amit a Google fejleszt. De hát ott se volt megoldva a dolog. A hibajegyben annyit írnak, hogy ki kell hagyni a manifestből a CodecPrivateData mezőt EAC-3 esetében és akkor menni fog ExoPlayerrel a lejátszás. Ez tök jó, csak én nem tudom kihagyni, hiszen MPEG-DASH formába fordítom át a smooth streaming manifestet, ahol meg kötelező valami init szegmenst kiszolgálni.
Eszembe jutott, hogy a Kodi féle inputstream adaptive is támogat smooth streaminget. Hátha ott megtalálom. De nem, az is csak AAC-t támogat.
Kétségbeesetten végigolvastam a teljes Microsoft doksit, megnéztem, hogy az mp4ffben nincs-e esetleg valami parser a problémámra, de nem volt. Belebotlottam viszont ebbe a beszélgetésbe pont az mp4ff oldalán:
A little more digging reveals that the CodecPrivateData should be a hex-encoded version of the the WAVE_FORMAT_EXTENSIBLE structure when the the AudioTab="65534". That format seems to be specified at https://learn.microsoft.com/en-us/windows/win32/api/mmeapi/ns-mmeapi-waveformatex. It doesn't help me to decipher the full string, though.
Szóval kb ő is addig jutott, mint én. Ekkor már nagyon csalódott voltam. Reménytelennek tűnt a helyzet. Még csak azt se tudom megcsinálni, hogy átkonvertálok csomó különböző hangot smooth streamingre, aztán összehasonlítom, hogy mi változik, ha pl több hangcsatornám van. Mivel ugye nincs publikusan elérhető szerver szoftver ingyen.
Aztán találtam egy ilyen hibajegyet is: [link]. Ráadásul ez le is van zárva, mint megoldott. A user pontosan azt kéri, amire nekem szükségem van. Legyen támogatott az EAC-3 kodek. És nem akárkitől, hanem a canalplustól, aki azért eléggé az élen jár nagy szolgáltatóként a streamingben. Tökre megörültem, hogy végre van egy sikeresen lezárt hibajegy. De akárhogy nézem, ide se került be a megoldás. Csak lezárták a hibajegyet.
De valahogy mégis megoldják a szerverek, hiszen létezik csomó helyen ez a formátum...
Végső kétségbeesésemben elkezdtem gyűjteni az összes mintát, amivel eddig találkoztam.
000603000000AF87FBA7022DFB42A4D405CD93843BDD0600200400
00063F000000AF87FBA7022DFB42A4D405CD93843BDD0700200F00
00063F000000AF87FBA7022DFB42A4D405CD93843BDD0600200F00
00063f000000AF87FBA7022DFB42A4D405CD93843BDD0600200F00
Feltűnt, hogy mind azonos hosszúságú, mind 0006-tal kezdődik, illetve ami érdekes, hogy az AF87FBA7022DFB42A4D405CD93843BDD
fix értéknek tűnik. Utolsó elkeseredésemben rákerestem erre is, hátha... És ott volt a szemem előtt a megoldás!
def ComputeDolbyDigitalPlusSmoothStreamingInfo(track):
(channel_count, channel_mask) = ComputeDolbyDigitalPlusAudioChannelMask(track)
info = "0006" # 1536 in little-endian
mask_hex_be = "{0:0{1}x}".format(channel_mask, 4)
info += mask_hex_be[2:4]+mask_hex_be[0:2]+'0000'
info += "af87fba7022dfb42a4d405cd93843bdd"
info += track.info['sample_descriptions'][0]['dolby_digital_plus_info']['dec3_payload']
return (channel_count, info.lower())
Ráadásul pont arra használja, amire én, csak a fordítottjára. Smooth streaming manifest előállításra. Sok magyarázat ugyan nincs a kódban, de ezek alapján az látszik, hogy a 0006-ot beégeti, a channel_maskról pedig rájöttem, hogy az mp4ff-nek van erre is megoldása, csak kicsit masszírozni kell, hogy big endian hexa enkódolás legyen. Utána jön ez a fura valami, ami statikus, aztán pedig a nyers dec3 box hexába kódolva. Nekem pont ez az utóbbi kell.
Jó sokat kutattam ezután érdekességképp, hogy mi lehet az a hosszú, statikus hexa érték. Megtaláltam, itt: [link].
316 | MFAudioFormat_Dolby_DDPlus | Guid | a7fb87af-2d02-42fb-a4d4-05cd93843bdd
Aham, szóval egy sima UUID az egész, ami azonosítja a kodeket. Elég érdekes lépés Microsofttól, amikor eddig az volt a cél, hogy minél kisebb mennyiségű adat menjen az egyes médiákba és össze vissza packeljék, de ők tudják... Viszont a UUID-re így rákeresve azonnal több találat ugrott fel. Pl: [link]. Tök érdekes, hogy semmi doksi, csak úgy léteznek ezek az értékek ezer éves Windows forrás fájlokban.
Na mindegy, a lényeg, hogy ez alapján gyerekjáték lesz összerakni a lényeget. Szóval először szedjük ki a channel maszkot és a dec3 boxot, hiszen ez a lényeg a CodecPrivateData mezőben. Bár a channel maszkra nem lesz most szükség, mivel azt a box is tartalmazni fogja, ezért azzal külön nem foglalkozom.
var DDP_WAVEFORMAT_GUID = []byte{0xaf, 0x87, 0xfb, 0xa7, 0x02, 0x2d, 0xfb, 0x42, 0xa4, 0xd4, 0x05, 0xcd, 0x93, 0x84, 0x3b, 0xdd}
func extractDolbyDigitalPlusInfoFromSmoothData(info []byte) []byte {
if bytes.Compare(info[8:24], DDP_WAVEFORMAT_GUID) != 1 {
panic("Missing DDP_WAVEFORMAT_GUID")
}
dec3Payload := info[6+len(DDP_WAVEFORMAT_GUID):]
return dec3Payload
}
Ezzel meg is vagyunk, már csak a feldolgozás maradt hátra. Ezt gyorsan megtehetjük:
func main() {
customPrivateDataHex := "000603000000AF87FBA7022DFB42A4D405CD93843BDD0600200400"
customPrivateData, _ := hex.DecodeString(customPrivateDataHex)
dec3Payload := extractDolbyDigitalPlusInfoFromSmoothData(customPrivateData)
dec3Box, err := mp4.DecodeDec3(mp4.BoxHeader{}, 0, bytes.NewReader(dec3Payload))
if err != nil {
panic(err)
}
log.Printf("Dec3 box: %+v", dec3Box)
}
Aztán futtatva imátkozunk:
[steve@todo test]$ go run main.go
2025/02/15 02:57:29 Dec3 box: &{DataRate:192 NumIndSub:0 EC3Subs:[{FSCod:0 BSID:16 ASVC:0 BSMod:0 ACMod:2 LFEOn:0 NumDepSub:0 ChanLoc:0}] Reserved:[]}
Felraktam ide a demót egybe, mert a PH! kód formázója nem a legjobb.
Csodás! Mindezt összeollózva élvezhetem a magas bitrátás Dolby Digital Plus hangot az élő adáson és végre minden úgy működik ismét, ahogy szerettem volna.
De gondoltam, ha már lúd, legyen kövér... Feliratokkal eddig nem foglalkoztam, pedig néha az is van. Szóval...
Feliratok
Különlegességük, hogy nincsenek DRM védve, mint a hang és videó, viszont TTML formátumban jönnek, ugyanúgy mp4 konténerekbe csomagolt XML szerű formában. Szerencsére itt nincs CodecPrivateData-s szenvedés sem. Az alábbi módon készítek hozzá init szegmenst:
func NewSubInit(lang string, timeScale uint32) (mp4.InitSegment, error) {
init := mp4.NewMP4Init()
init.AddChild(mp4.NewFtyp("dash", 0, []string{"iso6", "piff"}))
moov := mp4.NewMoovBox()
init.AddChild(moov)
moov.AddChild(mp4.CreateMvhd())
moov.AddChild(mp4.NewMvexBox())
init.AddEmptyTrack(timeScale, "subtitle", lang)
trak := init.Moov.Trak
err := trak.SetStppDescriptor("http://www.w3.org/ns/ttml http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt http://www.w3.org/ns/ttml#metadata http://www.w3.org/ns/ttml#parameter http://www.w3.org/ns/ttml#styling http://www.w3.org/2001/XMLSchema-instance http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt.xsd", "", "")
if err != nil {
return mp4.InitSegment{}, err
}
return *init, nil
}
A nyelvet és a timeScale értéket simán a manifestből szedem, a többi meg mind statikus. A hosszú sztringet a linkekkel pedig egy másik streaming szolgáltató dash init szegmenséből szedtem. Működni látszik. De egyébként csak annyit megadva, hogy http://www.w3.org/ns/ttml
is működik.
No, hát ennyi lett volna az én kis streamerem. A végeredménnyel tényleg nagyon elégedett vagyok. Pláne, hogy egy gyenge kis Pi0 is simán elboldogul a feladattal, hogy pillanatok alatt újracsomagolja az adást. A család többi tagja is elégedetten használja. Ha nagyon akarnám, kiválthatnám még a Pi-t is azzal, hogy direktben a TV-re fordítok Kodi addont, de már volt egy Pi0-m amúgy is, illetve így ki tud szolgálni egy Pi 2 TV-t is, hiszen egy előfizetés két tévére szól. Ha pedig egyszerre kettő tévén megy az adás, akkor elég egyszer letölteni a Pi-n az adatot és helyi hálón leküldeni kétszer, ezzel is spórolva az adatforgalmon.
Remélem a szolgáltató most nem fog sokáig variálni. Mert azért kemény menet volt mindezt kikutatni. A Microsoft doksikkal ördögöt lehetne űzni.
Köszi, hogy elolvastad!