From 004f017f87c7e5a960d49b4600539ba3bf3645f1 Mon Sep 17 00:00:00 2001 From: Daniel Paulus Date: Sun, 21 Nov 2021 14:25:45 +0100 Subject: [PATCH 1/5] initial refactor --- main.go | 72 +++- screencapture/messageprocessor.go | 2 +- screencapture/messageprocessor_test.go | 6 +- screencapture/packet/sync_og.go | 4 +- screencapture/usbadapter-new.go | 163 +++++++++ screencapture/valeriainterface.go | 446 +++++++++++++++++++++++++ 6 files changed, 686 insertions(+), 7 deletions(-) create mode 100644 screencapture/usbadapter-new.go create mode 100644 screencapture/valeriainterface.go diff --git a/main.go b/main.go index fd46a83..1c09df0 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ Usage: qvh gstreamer [--pipeline=] [--examples] [--udid=] [-v] qvh diagnostics [--dump=] [--udid=] qvh --version | version + qvh test Options: @@ -88,13 +89,20 @@ The commands work as following: if err != nil { printErrJSON(err, "no device found to use") } - checkDeviceIsPaired(device) + //checkDeviceIsPaired(device) activateCommand, _ := arguments.Bool("activate") if activateCommand { activate(device) return } + + testcmd,_ := arguments.Bool("test") + if testcmd { + test(device) + return + } + audioCommand, _ := arguments.Bool("audio") if audioCommand { outfile, err := arguments.String("") @@ -318,6 +326,63 @@ func activate(device screencapture.IosDevice) { }) } +func test(device screencapture.IosDevice) { + log.SetLevel(log.DebugLevel) + var err error + device, err = screencapture.EnableQTConfig(device) + if err != nil { + printErrJSON(err, "Error enabling QT config") + return + } + + valeriaInterface := screencapture.NewValeriaInterface() + go func() { + err := valeriaInterface.StartReadLoop(device) + log.Info("Valeria read loop stopped.") + if err != nil { + log.Errorf("Valeria read loop stopped with error %v", err) + } + }() + err = valeriaInterface.Local.AwaitPing() + if err != nil { + log.Fatalf("ping timed out failed", err) + } + log.Info("Ping received, responding..") + err = valeriaInterface.Remote.Ping() + fatalIfErr(err, "failed sending Ping") + log.Info("Handshake complete, awaiting audio clock sync") + err = valeriaInterface.Local.AwaitAudioClockSync() + if err != nil { + log.Fatalf("audio clock sync failed", err) + } + log.Info("audio clock sync ok, enabling video") + err = valeriaInterface.Remote.EnableVideo() + fatalIfErr(err, "failed enabling video") + log.Infof("enabling audio") + err = valeriaInterface.Remote.EnableAudio() + fatalIfErr(err, "failed enabling audio") + log.Info("awaiting video clock sync") + err = valeriaInterface.Local.AwaitVideoClockSync() + fatalIfErr(err, "failed waiting for video clock sync") + log.Info("sending initial sample data request") + err = valeriaInterface.Remote.RequestSampleData() + fatalIfErr(err, "failed requesting sample data") + go func() { + for { + buf := valeriaInterface.Local.ReadSampleBuffer() + err := valeriaInterface.Remote.RequestSampleData() + if err != nil { + log.Fatalf("failed sending need") + } + log.Infof(buf.String()) + } + }() + log.Info("press ctrl+c to stop") + stopSignal := make(chan interface{}) + waitForSigInt(stopSignal) + <-stopSignal +} + func record(h264FilePath string, wavFilePath string, device screencapture.IosDevice) { log.Debugf("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath) @@ -435,6 +500,11 @@ func checkDeviceIsPaired(device screencapture.IosDevice) { log.Infof("found %s %s for udid %s", allValues["DeviceName"], allValues["ProductVersion"], dev.Properties.SerialNumber) } +func fatalIfErr(err error, msg string) { + if err != nil { + log.Fatalf("%s: %v", msg, err) + } +} func printErrJSON(err error, msg string) { printJSON(map[string]interface{}{ "original_error": err.Error(), diff --git a/screencapture/messageprocessor.go b/screencapture/messageprocessor.go index 47a60c0..aefd110 100644 --- a/screencapture/messageprocessor.go +++ b/screencapture/messageprocessor.go @@ -83,7 +83,7 @@ func (mp *MessageProcessor) handleSyncPacket(data []byte) { } log.Debugf("Rcv:%s", ogPacket.String()) - replyBytes := ogPacket.NewReply() + replyBytes := ogPacket.NewReply(0) log.Debugf("Send OG-REPLY {correlation:%x}", ogPacket.CorrelationID) mp.usbWriter.WriteDataToUsb(replyBytes) case packet.CWPA: diff --git a/screencapture/messageprocessor_test.go b/screencapture/messageprocessor_test.go index 48339d4..1ffbe4f 100644 --- a/screencapture/messageprocessor_test.go +++ b/screencapture/messageprocessor_test.go @@ -83,9 +83,9 @@ func TestMessageProcessorRespondsCorrectlyToSyncMessages(t *testing.T) { description: "Expect correct reply for cwpa", }, { - receivedData: loadFromFile("og-request")[4:], - expectedReply: [][]byte{loadFromFile("og-reply")}, - description: "Expect correct reply for og", + receivedData: loadFromFile("gocmd-request")[4:], + expectedReply: [][]byte{loadFromFile("gocmd-reply")}, + description: "Expect correct reply for gocmd", }, { receivedData: loadFromFile("stop-request")[4:], diff --git a/screencapture/packet/sync_og.go b/screencapture/packet/sync_og.go index 886c2d7..de45ec4 100644 --- a/screencapture/packet/sync_og.go +++ b/screencapture/packet/sync_og.go @@ -25,12 +25,12 @@ func NewSyncOgPacketFromBytes(data []byte) (SyncOgPacket, error) { } //NewReply returns a []byte containing the default reply for a SyncOgPacket -func (sp SyncOgPacket) NewReply() []byte { +func (sp SyncOgPacket) NewReply(response uint64) []byte { responseBytes := make([]byte, 24) binary.LittleEndian.PutUint32(responseBytes, 24) binary.LittleEndian.PutUint32(responseBytes[4:], ReplyPacketMagic) binary.LittleEndian.PutUint64(responseBytes[8:], sp.CorrelationID) - binary.LittleEndian.PutUint64(responseBytes[16:], 0) + binary.LittleEndian.PutUint64(responseBytes[16:], response) return responseBytes diff --git a/screencapture/usbadapter-new.go b/screencapture/usbadapter-new.go new file mode 100644 index 0000000..3a656ce --- /dev/null +++ b/screencapture/usbadapter-new.go @@ -0,0 +1,163 @@ +package screencapture + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/pkg/errors" + + "github.com/google/gousb" + log "github.com/sirupsen/logrus" +) + +//UsbAdapterNew reads and writes from AV Quicktime USB Bulk endpoints +type UsbAdapterNew struct { + outEndpoint *gousb.OutEndpoint + Dump bool + DumpOutWriter io.Writer + DumpInWriter io.Writer + stream *gousb.ReadStream + usbDevice *gousb.Device + contextClose func() + iface *gousb.Interface + iosDevice IosDevice +} + +//WriteDataToUsb implements the UsbWriter interface and sends the byte array to the usb bulk endpoint. +func (usbAdapter *UsbAdapterNew) WriteDataToUsb(bytes []byte) error { + _, err := usbAdapter.outEndpoint.Write(bytes) + if err != nil { + return err + } + if usbAdapter.Dump { + _, err := usbAdapter.DumpOutWriter.Write(bytes) + if err != nil { + log.Fatalf("Failed dumping data:%v", err) + } + } + return nil +} + +func (usbAdapter *UsbAdapterNew) InitializeUSB(device IosDevice) error { + ctx, cleanUp := createContext() + usbAdapter.contextClose = cleanUp + usbAdapter.outEndpoint = &gousb.OutEndpoint{} + usbAdapter.stream = &gousb.ReadStream{} + usbAdapter.usbDevice = &gousb.Device{} + usbAdapter.iface = &gousb.Interface{} + usbAdapter.iosDevice = device + + usbDevice, err := OpenDevice(ctx, device) + if err != nil { + return err + } + if !device.IsActivated() { + return errors.New("device not activated for screen mirroring") + } + + confignum, _ := usbDevice.ActiveConfigNum() + log.Debugf("Config is active: %d, QT config is: %d", confignum, device.QTConfigIndex) + + config, err := usbDevice.Config(device.QTConfigIndex) + if err != nil { + return errors.New("Could not retrieve config") + } + + log.Debugf("QT Config is active: %s", config.String()) + + iface, err := findAndClaimQuickTimeInterface(config) + if err != nil { + log.Debug("could not get Quicktime Interface") + return err + } + log.Debugf("Got QT iface:%s", iface.String()) + + inboundBulkEndpointIndex, inboundBulkEndpointAddress, err := findBulkEndpoint(iface.Setting, gousb.EndpointDirectionIn) + if err != nil { + return err + } + + outboundBulkEndpointIndex, outboundBulkEndpointAddress, err := findBulkEndpoint(iface.Setting, gousb.EndpointDirectionOut) + if err != nil { + return err + } + + err = clearFeature(usbDevice, inboundBulkEndpointAddress, outboundBulkEndpointAddress) + if err != nil { + return err + } + + inEndpoint, err := iface.InEndpoint(inboundBulkEndpointIndex) + if err != nil { + log.Error("couldnt get InEndpoint") + return err + } + log.Debugf("Inbound Bulk: %s", inEndpoint.String()) + + outEndpoint, err := iface.OutEndpoint(outboundBulkEndpointIndex) + if err != nil { + log.Error("couldnt get OutEndpoint") + return err + } + + stream, err := inEndpoint.NewStream(4096, 5) + if err != nil { + log.Fatal("couldnt create stream") + return err + } + log.Debug("Endpoint claimed") + log.Debugf("Outbound Bulk: %s", outEndpoint.String()) + usbAdapter.outEndpoint = outEndpoint + usbAdapter.stream = stream + usbAdapter.usbDevice = usbDevice + usbAdapter.iface = iface + + return nil +} + +func (usbAdapter *UsbAdapterNew) Close() error { + log.Info("Closing usb stream") + + err := usbAdapter.stream.Close() + if err != nil { + log.Error("Error closing stream", err) + } + log.Info("Closing usb interface") + usbAdapter.iface.Close() + + sendQTDisableConfigControlRequest(usbAdapter.usbDevice) + log.Debug("Resetting device config") + _, err = usbAdapter.usbDevice.Config(usbAdapter.iosDevice.UsbMuxConfigIndex) + if err != nil { + log.Warn(err) + } + usbAdapter.contextClose() + return nil +} + +func (usbAdapter *UsbAdapterNew) ReadFrame() ([]byte, error) { + lengthBuffer := make([]byte, 4) + for { + n, err := io.ReadFull(usbAdapter.stream, lengthBuffer) + if err != nil { + return []byte{}, fmt.Errorf("failed reading 4bytes length with err:%s only received: %d", err, n) + } + //the 4 bytes header are included in the length, so we need to subtract them + //here to know how long the payload will be + length := binary.LittleEndian.Uint32(lengthBuffer) - 4 + dataBuffer := make([]byte, length) + + n, err = io.ReadFull(usbAdapter.stream, dataBuffer) + if err != nil { + return []byte{}, err + } + if usbAdapter.Dump { + _, err := usbAdapter.DumpInWriter.Write(dataBuffer) + if err != nil { + log.Fatalf("Failed dumping data:%v", err) + } + } + return dataBuffer, nil + } +} diff --git a/screencapture/valeriainterface.go b/screencapture/valeriainterface.go new file mode 100644 index 0000000..0c5d830 --- /dev/null +++ b/screencapture/valeriainterface.go @@ -0,0 +1,446 @@ +package screencapture + +import ( + "encoding/binary" + "fmt" + "github.com/danielpaulus/quicktime_video_hack/screencapture/coremedia" + "github.com/danielpaulus/quicktime_video_hack/screencapture/packet" + log "github.com/sirupsen/logrus" + "time" +) + +type ValeriaInterface struct { + Local LocalValeriaApi + Remote DeviceValeriaAPI + errorChannel chan error + closeChannel chan interface{} +} + +type DataHolder struct { + localAudioClock coremedia.CMClock + deviceAudioClockRef packet.CFTypeID + needClockRef packet.CFTypeID + clock coremedia.CMClock + startTimeLocalAudioClock coremedia.CMTime + lastEatFrameReceivedLocalAudioClockTime coremedia.CMTime + startTimeDeviceAudioClock coremedia.CMTime + lastEatFrameReceivedDeviceAudioClockTime coremedia.CMTime + audioSamplesReceived uint64 + firstAudioTimeTaken bool + videoSamplesReceived uint64 +} + +type LocalValeriaApi struct { + remote DeviceValeriaAPI + pingChannel chan error + audioClockChannel chan error + dataHolder DataHolder + videoClockChannel chan error + sampleDataChannel chan coremedia.CMSampleBuffer + audioReleaseChannel chan error + videoReleaseChannel chan error +} + +type DeviceValeriaAPI struct { + usbAdapter *UsbAdapterNew + dataHolder DataHolder +} + +func NewValeriaInterface() ValeriaInterface { + dataHolder := DataHolder{} + local := LocalValeriaApi{ + pingChannel: make(chan error, 1), + audioClockChannel: make(chan error, 1), + videoClockChannel: make(chan error, 1), + audioReleaseChannel: make(chan error, 1), + videoReleaseChannel: make(chan error, 1), + dataHolder: dataHolder, + sampleDataChannel: make(chan coremedia.CMSampleBuffer, 50), + } + remote := DeviceValeriaAPI{dataHolder: dataHolder} + valeriaIface := ValeriaInterface{Local: local, + errorChannel: make(chan error, 1), + closeChannel: make(chan interface{}), + Remote: remote, + } + return valeriaIface +} + +func (l LocalValeriaApi) AwaitAudioClockRelease() error { + return awaitOrTimeout(l.audioReleaseChannel, "audio clock release") +} + +func (l LocalValeriaApi) AwaitVideoClockRelease() error { + return awaitOrTimeout(l.videoReleaseChannel, "video clock release") +} + +func (l LocalValeriaApi) AwaitVideoClockSync() error { + return awaitOrTimeout(l.videoClockChannel, "video clock") +} + +func (l LocalValeriaApi) AwaitAudioClockSync() error { + return awaitOrTimeout(l.audioClockChannel, "audio clock") +} + +func (l LocalValeriaApi) AwaitPing() error { + return awaitOrTimeout(l.pingChannel, "waiting for ping") +} + +func awaitOrTimeout(channel chan error, loggerTag string) error { + select { + case <-time.After(5 * time.Second): + return fmt.Errorf("%s timed out. restart the device please it might be buggy", loggerTag) + case err := <-channel: + if err != nil { + return fmt.Errorf("failed '%s' with device %v", loggerTag, err) + } + } + log.Infof("%s succeeded", loggerTag) + return nil +} + +func (l LocalValeriaApi) ping() { + l.pingChannel <- nil +} + +//I don't know what the !go command is for. It seems we always just return 0 and it works. +func (l LocalValeriaApi) gocmd(unknown uint32) uint64 { + log.Debugf("go! %d", unknown) + return 0 +} + +func (l *LocalValeriaApi) setupAudioClock(deviceClockRef packet.CFTypeID) packet.CFTypeID { + clockRef := deviceClockRef + 1000 + + l.dataHolder.localAudioClock = coremedia.NewCMClockWithHostTime(clockRef) + l.dataHolder.deviceAudioClockRef = deviceClockRef + l.audioClockChannel <- nil + return clockRef +} + +//this message is always the same, so we just prepare it once and send the same bytes all the time +var needMessage []byte + +func (l *LocalValeriaApi) setupVideoClock(deviceClockRef packet.CFTypeID) packet.CFTypeID { + l.dataHolder.needClockRef = deviceClockRef + needMessage = packet.AsynNeedPacketBytes(deviceClockRef) + l.videoClockChannel <- nil + return deviceClockRef + 0x1000AF +} + +func (l LocalValeriaApi) setupMainClock(ref packet.CFTypeID) packet.CFTypeID { + clockRef := ref + 0x10000 + l.dataHolder.clock = coremedia.NewCMClockWithHostTime(clockRef) + return clockRef +} + +func (l LocalValeriaApi) time() coremedia.CMTime { + return l.dataHolder.clock.GetTime() +} + +func (l LocalValeriaApi) stop() { + log.Info("device sent STOP command") +} + +func (l LocalValeriaApi) skew() float64 { + return coremedia.CalculateSkew( + l.dataHolder.startTimeLocalAudioClock, + l.dataHolder.lastEatFrameReceivedLocalAudioClockTime, + l.dataHolder.startTimeDeviceAudioClock, + l.dataHolder.lastEatFrameReceivedDeviceAudioClockTime) +} + +//ReadSampleBuffer blocks until a buffer is received or the interface is closed +func (l LocalValeriaApi) ReadSampleBuffer() coremedia.CMSampleBuffer { + return <-l.sampleDataChannel +} + +func (l LocalValeriaApi) receiveAudioSample(buf coremedia.CMSampleBuffer) { + if !l.dataHolder.firstAudioTimeTaken { + l.dataHolder.startTimeDeviceAudioClock = buf.OutputPresentationTimestamp + l.dataHolder.startTimeLocalAudioClock = l.dataHolder.localAudioClock.GetTime() + l.dataHolder.lastEatFrameReceivedDeviceAudioClockTime = buf.OutputPresentationTimestamp + l.dataHolder.lastEatFrameReceivedLocalAudioClockTime = l.dataHolder.startTimeLocalAudioClock + l.dataHolder.firstAudioTimeTaken = true + } else { + l.dataHolder.lastEatFrameReceivedDeviceAudioClockTime = buf.OutputPresentationTimestamp + l.dataHolder.lastEatFrameReceivedLocalAudioClockTime = l.dataHolder.localAudioClock.GetTime() + } + + l.sampleDataChannel <- buf + if log.IsLevelEnabled(log.DebugLevel) { + l.dataHolder.audioSamplesReceived++ + if l.dataHolder.audioSamplesReceived%100 == 0 { + log.Debugf("RCV Audio Samples:%d", l.dataHolder.audioSamplesReceived) + } + } +} + +func (l LocalValeriaApi) feed(buf coremedia.CMSampleBuffer) { + l.sampleDataChannel <- buf + if log.IsLevelEnabled(log.DebugLevel) { + l.dataHolder.videoSamplesReceived++ + if l.dataHolder.videoSamplesReceived%500 == 0 { + log.Debugf("Rcv'd(%d) last:%s", l.dataHolder.videoSamplesReceived, buf.String()) + l.dataHolder.videoSamplesReceived = 0 + } + } +} + +func (l LocalValeriaApi) timeJump(unknown []byte) { + +} + +func (l LocalValeriaApi) setProperties(property coremedia.StringKeyEntry, clockRef packet.CFTypeID) { + +} + +func (l LocalValeriaApi) setClockRate(clockRef packet.CFTypeID, cmTime coremedia.CMTime, rate1 float32, rate2 float32) { + +} + +func (l LocalValeriaApi) setTimeBase(ref packet.CFTypeID, ref2 packet.CFTypeID) { + +} + +func (l LocalValeriaApi) release(clockRef packet.CFTypeID) { + if clockRef == l.dataHolder.needClockRef { + l.videoReleaseChannel <- nil + return + } + if clockRef == l.dataHolder.localAudioClock.ID { + l.audioReleaseChannel <- nil + return + } + log.Warnf("release for unknown clock received %d", clockRef) +} + +func (d DeviceValeriaAPI) RequestSampleData() error { + log.Debugf("Send NEED %x", d.dataHolder.needClockRef) + return d.usbAdapter.WriteDataToUsb(needMessage) +} + +func (d DeviceValeriaAPI) EnableVideo() error { + deviceInfo := packet.NewAsynHpd1Packet(packet.CreateHpd1DeviceInfoDict()) + log.Debug("Sending ASYN HPD1") + err := d.usbAdapter.WriteDataToUsb(deviceInfo) + if err != nil { + return err + } + log.Debug("Sending ASYN HPD1") + return d.usbAdapter.WriteDataToUsb(deviceInfo) +} + +func (d DeviceValeriaAPI) EnableAudio() error { + + deviceInfo1 := packet.NewAsynHpa1Packet(packet.CreateHpa1DeviceInfoDict(), d.dataHolder.deviceAudioClockRef) + log.Debug("Sending ASYN HPA1") + return d.usbAdapter.WriteDataToUsb(deviceInfo1) +} + +func (d DeviceValeriaAPI) Ping() error { + return d.usbAdapter.WriteDataToUsb(packet.NewPingPacketAsBytes()) +} + +// StartReadLoop claims&opens the USB Device and starts listening to RPC calls +// and blocks until ValeriaInterface is closed or an error occurs. +func (v *ValeriaInterface) StartReadLoop(device IosDevice) error { + usbAdapter := &UsbAdapterNew{} + err := usbAdapter.InitializeUSB(device) + if err != nil { + return fmt.Errorf("failed initializing usb with error %v", err) + } + v.Remote.usbAdapter = usbAdapter + return readLoop(v, usbAdapter) +} + +//readLoop reads messages sent by the device and dispatches them to the local api +func readLoop(v *ValeriaInterface, usbAdapter *UsbAdapterNew) error { + for { + select { + case err := <-v.errorChannel: + return err + case <-v.closeChannel: + return nil + default: + frame, err := usbAdapter.ReadFrame() + if err != nil { + return err + } + handleFrame(frame, v, usbAdapter) + } + + } +} + +// Decode Remote rpc calls and forward them to the local Valeria API +func handleFrame(data []byte, valeria *ValeriaInterface, usbAdapter *UsbAdapterNew) { + switch binary.LittleEndian.Uint32(data) { + case packet.PingPacketMagic: + valeria.Local.ping() + case packet.SyncPacketMagic: + err := handleSyncPacket(data, valeria, usbAdapter) + if err != nil { + valeria.errorChannel <- err + return + } + return + case packet.AsynPacketMagic: + err := handleAsyncPacket(data, valeria) + if err != nil { + valeria.errorChannel <- err + return + } + return + default: + valeria.errorChannel <- fmt.Errorf("received unknown packet type: %x", data[:4]) + } +} + +func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapterNew) error { + switch binary.LittleEndian.Uint32(data[12:]) { + case packet.OG: + ogPacket, err := packet.NewSyncOgPacketFromBytes(data) + if err != nil { + log.Error("Error parsing OG packet", err) + } + log.Debugf("Rcv:%s", ogPacket.String()) + response := valeria.Local.gocmd(ogPacket.Unknown) + replyBytes := ogPacket.NewReply(response) + return adapter.WriteDataToUsb(replyBytes) + case packet.CWPA: + cwpaPacket, err := packet.NewSyncCwpaPacketFromBytes(data) + if err != nil { + return fmt.Errorf("failed parsing cwpa packet %v", err) + } + log.Debugf("Rcv:%s", cwpaPacket.String()) + clockRef := valeria.Local.setupAudioClock(cwpaPacket.DeviceClockRef) + + log.Debugf("Send CWPA-RPLY {correlation:%x, clockRef:%x}", cwpaPacket.CorrelationID, clockRef) + return adapter.WriteDataToUsb(cwpaPacket.NewReply(clockRef)) + + case packet.CVRP: + cvrpPacket, err := packet.NewSyncCvrpPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing CVRP packet %v", err) + } + log.Debugf("Rcv:%s", cvrpPacket.String()) + videoClockRef := valeria.Local.setupVideoClock(cvrpPacket.DeviceClockRef) + + log.Debugf("Send CVRP-RPLY {correlation:%x, clockRef:%x}", cvrpPacket.CorrelationID, videoClockRef) + return adapter.WriteDataToUsb(cvrpPacket.NewReply(videoClockRef)) + case packet.CLOK: + clokPacket, err := packet.NewSyncClokPacketFromBytes(data) + if err != nil { + log.Error("Failed parsing Clok Packet", err) + } + log.Debugf("Rcv:%s", clokPacket.String()) + clockRef := valeria.Local.setupMainClock(clokPacket.ClockRef) + + log.Debugf("Send CLOK-RPLY {correlation:%x, clockRef:%x}", clokPacket.CorrelationID, clockRef) + return adapter.WriteDataToUsb(clokPacket.NewReply(clockRef)) + case packet.TIME: + timePacket, err := packet.NewSyncTimePacketFromBytes(data) + if err != nil { + log.Error("Error parsing TIME SYNC packet", err) + } + log.Debugf("Rcv:%s", timePacket.String()) + timeToSend := valeria.Local.time() + replyBytes, err := timePacket.NewReply(timeToSend) + if err != nil { + return fmt.Errorf("could not create SYNC TIME REPLY") + } + log.Debugf("Send TIME-REPLY {correlation:%x, time:%s}", timePacket.CorrelationID, timeToSend) + return adapter.WriteDataToUsb(replyBytes) + //TODO: turn into nice API function + case packet.AFMT: + afmtPacket, err := packet.NewSyncAfmtPacketFromBytes(data) + if err != nil { + log.Error("Error parsing SYNC AFMT packet", err) + } + log.Debugf("Rcv:%s", afmtPacket.String()) + + replyBytes := afmtPacket.NewReply() + log.Debugf("Send AFMT-REPLY {correlation:%x}", afmtPacket.CorrelationID) + return adapter.WriteDataToUsb(replyBytes) + case packet.SKEW: + skewPacket, err := packet.NewSyncSkewPacketFromBytes(data) + if err != nil { + log.Error("Error parsing SYNC SKEW packet", err) + } + skewValue := valeria.Local.skew() + log.Debugf("Rcv:%s Reply:%f", skewPacket.String(), skewValue) + return adapter.WriteDataToUsb(skewPacket.NewReply(skewValue)) + case packet.STOP: + stopPacket, err := packet.NewSyncStopPacketFromBytes(data) + if err != nil { + log.Error("Error parsing SYNC STOP packet", err) + } + valeria.Local.stop() + log.Debugf("Rcv:%s", stopPacket.String()) + return adapter.WriteDataToUsb(stopPacket.NewReply()) + default: + return fmt.Errorf("received unknown sync packet type: %x", data) + } +} + +func handleAsyncPacket(data []byte, valeria *ValeriaInterface) error { + switch binary.LittleEndian.Uint32(data[12:]) { + case packet.EAT: + eatPacket, err := packet.NewAsynCmSampleBufPacketFromBytes(data) + if err != nil { + return fmt.Errorf("eat packet could not be unmarshalled %v", err) + } + valeria.Local.receiveAudioSample(eatPacket.CMSampleBuf) + return nil + case packet.FEED: + feedPacket, err := packet.NewAsynCmSampleBufPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing FEED packet: %x %s", data, err) + } + valeria.Local.feed(feedPacket.CMSampleBuf) + return nil + case packet.SPRP: + sprpPacket, err := packet.NewAsynSprpPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing SPRP packet %v", err) + } + valeria.Local.setProperties(sprpPacket.Property, sprpPacket.ClockRef) + log.Debugf("Rcv:%s", sprpPacket.String()) + return nil + case packet.TJMP: + tjmpPacket, err := packet.NewAsynTjmpPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing tjmp packet %v", err) + } + valeria.Local.timeJump(tjmpPacket.Unknown) + log.Debugf("Rcv:%s", tjmpPacket.String()) + return nil + case packet.SRAT: + sratPacket, err := packet.NewAsynSratPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing srat packet %v", err) + } + valeria.Local.setClockRate(sratPacket.ClockRef, sratPacket.Time, sratPacket.Rate1, sratPacket.Rate2) + log.Debugf("Rcv:%s", sratPacket.String()) + return nil + case packet.TBAS: + tbasPacket, err := packet.NewAsynTbasPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing tbas packet %v", err) + } + valeria.Local.setTimeBase(tbasPacket.ClockRef, tbasPacket.SomeOtherRef) + log.Debugf("Rcv:%s", tbasPacket.String()) + return nil + case packet.RELS: + relsPacket, err := packet.NewAsynRelsPacketFromBytes(data) + if err != nil { + return fmt.Errorf("error parsing RELS packet %v", err) + } + valeria.Local.release(relsPacket.ClockRef) + log.Debugf("Rcv:%s", relsPacket.String()) + return nil + default: + return fmt.Errorf("received unknown async packet type: %x", data) + } +} From 85e007950895585e85e425c03f3fdbc1cad77114 Mon Sep 17 00:00:00 2001 From: Daniel Paulus Date: Sun, 21 Nov 2021 16:09:46 +0100 Subject: [PATCH 2/5] implement closing --- main.go | 57 +++++++++++++++++---- screencapture/interfaces.go | 6 +++ screencapture/valeriainterface.go | 83 +++++++++++++++++-------------- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/main.go b/main.go index 1c09df0..ea51703 100644 --- a/main.go +++ b/main.go @@ -97,7 +97,7 @@ The commands work as following: return } - testcmd,_ := arguments.Bool("test") + testcmd, _ := arguments.Bool("test") if testcmd { test(device) return @@ -335,19 +335,30 @@ func test(device screencapture.IosDevice) { return } - valeriaInterface := screencapture.NewValeriaInterface() + usbAdapter := &screencapture.UsbAdapterNew{} + err = usbAdapter.InitializeUSB(device) + if err != nil { + printErrJSON(err, "failed initializing usb with error") + return + } + + valeriaInterface := screencapture.NewValeriaInterface(usbAdapter) + defer CloseAll(usbAdapter, valeriaInterface) go func() { - err := valeriaInterface.StartReadLoop(device) + err := valeriaInterface.StartReadLoop() log.Info("Valeria read loop stopped.") if err != nil { log.Errorf("Valeria read loop stopped with error %v", err) } }() + err = valeriaInterface.Local.AwaitPing() if err != nil { - log.Fatalf("ping timed out failed", err) + log.Errorf("ping timed out failed", err) + return } log.Info("Ping received, responding..") + err = valeriaInterface.Remote.Ping() fatalIfErr(err, "failed sending Ping") log.Info("Handshake complete, awaiting audio clock sync") @@ -377,10 +388,38 @@ func test(device screencapture.IosDevice) { log.Infof(buf.String()) } }() - log.Info("press ctrl+c to stop") - stopSignal := make(chan interface{}) - waitForSigInt(stopSignal) - <-stopSignal + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c +} + +func CloseAll(usbAdapter *screencapture.UsbAdapterNew, valeriaInterface screencapture.ValeriaInterface) { + log.Info("stopping audio") + err := valeriaInterface.Remote.StopAudio() + if err != nil { + log.Errorf("error stopping audio", err) + } + + log.Info("stopping video") + err = valeriaInterface.Remote.StopVideo() + if err != nil { + log.Errorf("error stopping video", err) + } + log.Info("awaiting audio release") + err = valeriaInterface.Local.AwaitAudioClockRelease() + if err != nil { + log.Errorf("error waiting audio clock release", err) + } + + log.Info("awaiting video release") + err = valeriaInterface.Local.AwaitVideoClockRelease() + if err != nil { + log.Errorf("error waiting video clock release", err) + } + + log.Info("shutting down usbadapter") + err = usbAdapter.Close() + log.Info("Stream closed successfullly, good bye :-)") } func record(h264FilePath string, wavFilePath string, device screencapture.IosDevice) { @@ -502,7 +541,7 @@ func checkDeviceIsPaired(device screencapture.IosDevice) { func fatalIfErr(err error, msg string) { if err != nil { - log.Fatalf("%s: %v", msg, err) + log.Errorf("%s: %v", msg, err) } } func printErrJSON(err error, msg string) { diff --git a/screencapture/interfaces.go b/screencapture/interfaces.go index 7e19e3a..ba1a5b9 100644 --- a/screencapture/interfaces.go +++ b/screencapture/interfaces.go @@ -18,3 +18,9 @@ type UsbDataReceiver interface { type UsbWriter interface { WriteDataToUsb(data []byte) } + +//UsbWriter can be used to send data to a USB Endpoint +type UsbWriterNew interface { + WriteDataToUsb(data []byte) error + ReadFrame() ([]byte, error) +} diff --git a/screencapture/valeriainterface.go b/screencapture/valeriainterface.go index 0c5d830..b1d243a 100644 --- a/screencapture/valeriainterface.go +++ b/screencapture/valeriainterface.go @@ -19,7 +19,7 @@ type ValeriaInterface struct { type DataHolder struct { localAudioClock coremedia.CMClock deviceAudioClockRef packet.CFTypeID - needClockRef packet.CFTypeID + remoteVideoClockRef packet.CFTypeID clock coremedia.CMClock startTimeLocalAudioClock coremedia.CMTime lastEatFrameReceivedLocalAudioClockTime coremedia.CMTime @@ -28,6 +28,7 @@ type DataHolder struct { audioSamplesReceived uint64 firstAudioTimeTaken bool videoSamplesReceived uint64 + localVideoClockRef packet.CFTypeID } type LocalValeriaApi struct { @@ -42,11 +43,11 @@ type LocalValeriaApi struct { } type DeviceValeriaAPI struct { - usbAdapter *UsbAdapterNew + usbAdapter UsbWriterNew dataHolder DataHolder } -func NewValeriaInterface() ValeriaInterface { +func NewValeriaInterface(usbAdapter UsbWriterNew) ValeriaInterface { dataHolder := DataHolder{} local := LocalValeriaApi{ pingChannel: make(chan error, 1), @@ -57,7 +58,7 @@ func NewValeriaInterface() ValeriaInterface { dataHolder: dataHolder, sampleDataChannel: make(chan coremedia.CMSampleBuffer, 50), } - remote := DeviceValeriaAPI{dataHolder: dataHolder} + remote := DeviceValeriaAPI{dataHolder: dataHolder, usbAdapter: usbAdapter} valeriaIface := ValeriaInterface{Local: local, errorChannel: make(chan error, 1), closeChannel: make(chan interface{}), @@ -66,6 +67,20 @@ func NewValeriaInterface() ValeriaInterface { return valeriaIface } +// StartReadLoop claims&opens the USB Device and starts listening to RPC calls +// and blocks until ValeriaInterface is closed or an error occurs. +func (v *ValeriaInterface) StartReadLoop() error { + return readLoop(v) +} + +func (d DeviceValeriaAPI) StopAudio() error { + return d.usbAdapter.WriteDataToUsb(packet.NewAsynHPA0(d.dataHolder.deviceAudioClockRef)) +} + +func (d DeviceValeriaAPI) StopVideo() error { + return d.usbAdapter.WriteDataToUsb(packet.NewAsynHPD0()) +} + func (l LocalValeriaApi) AwaitAudioClockRelease() error { return awaitOrTimeout(l.audioReleaseChannel, "audio clock release") } @@ -122,13 +137,14 @@ func (l *LocalValeriaApi) setupAudioClock(deviceClockRef packet.CFTypeID) packet var needMessage []byte func (l *LocalValeriaApi) setupVideoClock(deviceClockRef packet.CFTypeID) packet.CFTypeID { - l.dataHolder.needClockRef = deviceClockRef + l.dataHolder.remoteVideoClockRef = deviceClockRef + l.dataHolder.localVideoClockRef = deviceClockRef + 0x1000AF needMessage = packet.AsynNeedPacketBytes(deviceClockRef) l.videoClockChannel <- nil - return deviceClockRef + 0x1000AF + return l.dataHolder.localVideoClockRef } -func (l LocalValeriaApi) setupMainClock(ref packet.CFTypeID) packet.CFTypeID { +func (l *LocalValeriaApi) setupMainClock(ref packet.CFTypeID) packet.CFTypeID { clockRef := ref + 0x10000 l.dataHolder.clock = coremedia.NewCMClockWithHostTime(clockRef) return clockRef @@ -204,19 +220,24 @@ func (l LocalValeriaApi) setTimeBase(ref packet.CFTypeID, ref2 packet.CFTypeID) } func (l LocalValeriaApi) release(clockRef packet.CFTypeID) { - if clockRef == l.dataHolder.needClockRef { + if clockRef == l.dataHolder.localVideoClockRef { l.videoReleaseChannel <- nil return } - if clockRef == l.dataHolder.localAudioClock.ID { + if clockRef == l.dataHolder.clock.ID { l.audioReleaseChannel <- nil return } - log.Warnf("release for unknown clock received %d", clockRef) + log.Warnf( + ` + release for unknown clock received %x -- localaudio:%x remoteaudio:%x + remotevideo:%x localvideo:%x mainclock:%x`, + clockRef, l.dataHolder.localAudioClock.ID, l.dataHolder.deviceAudioClockRef, + l.dataHolder.remoteVideoClockRef, l.dataHolder.localVideoClockRef, l.dataHolder.clock.ID) } func (d DeviceValeriaAPI) RequestSampleData() error { - log.Debugf("Send NEED %x", d.dataHolder.needClockRef) + log.Debugf("Send NEED %x", d.dataHolder.remoteVideoClockRef) return d.usbAdapter.WriteDataToUsb(needMessage) } @@ -242,20 +263,8 @@ func (d DeviceValeriaAPI) Ping() error { return d.usbAdapter.WriteDataToUsb(packet.NewPingPacketAsBytes()) } -// StartReadLoop claims&opens the USB Device and starts listening to RPC calls -// and blocks until ValeriaInterface is closed or an error occurs. -func (v *ValeriaInterface) StartReadLoop(device IosDevice) error { - usbAdapter := &UsbAdapterNew{} - err := usbAdapter.InitializeUSB(device) - if err != nil { - return fmt.Errorf("failed initializing usb with error %v", err) - } - v.Remote.usbAdapter = usbAdapter - return readLoop(v, usbAdapter) -} - //readLoop reads messages sent by the device and dispatches them to the local api -func readLoop(v *ValeriaInterface, usbAdapter *UsbAdapterNew) error { +func readLoop(v *ValeriaInterface) error { for { select { case err := <-v.errorChannel: @@ -263,23 +272,23 @@ func readLoop(v *ValeriaInterface, usbAdapter *UsbAdapterNew) error { case <-v.closeChannel: return nil default: - frame, err := usbAdapter.ReadFrame() + frame, err := v.Remote.usbAdapter.ReadFrame() if err != nil { return err } - handleFrame(frame, v, usbAdapter) + handleFrame(frame, v) } } } // Decode Remote rpc calls and forward them to the local Valeria API -func handleFrame(data []byte, valeria *ValeriaInterface, usbAdapter *UsbAdapterNew) { +func handleFrame(data []byte, valeria *ValeriaInterface) { switch binary.LittleEndian.Uint32(data) { case packet.PingPacketMagic: valeria.Local.ping() case packet.SyncPacketMagic: - err := handleSyncPacket(data, valeria, usbAdapter) + err := handleSyncPacket(data, valeria) if err != nil { valeria.errorChannel <- err return @@ -297,7 +306,7 @@ func handleFrame(data []byte, valeria *ValeriaInterface, usbAdapter *UsbAdapterN } } -func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapterNew) error { +func handleSyncPacket(data []byte, valeria *ValeriaInterface) error { switch binary.LittleEndian.Uint32(data[12:]) { case packet.OG: ogPacket, err := packet.NewSyncOgPacketFromBytes(data) @@ -307,7 +316,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte log.Debugf("Rcv:%s", ogPacket.String()) response := valeria.Local.gocmd(ogPacket.Unknown) replyBytes := ogPacket.NewReply(response) - return adapter.WriteDataToUsb(replyBytes) + return valeria.Remote.usbAdapter.WriteDataToUsb(replyBytes) case packet.CWPA: cwpaPacket, err := packet.NewSyncCwpaPacketFromBytes(data) if err != nil { @@ -317,7 +326,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte clockRef := valeria.Local.setupAudioClock(cwpaPacket.DeviceClockRef) log.Debugf("Send CWPA-RPLY {correlation:%x, clockRef:%x}", cwpaPacket.CorrelationID, clockRef) - return adapter.WriteDataToUsb(cwpaPacket.NewReply(clockRef)) + return valeria.Remote.usbAdapter.WriteDataToUsb(cwpaPacket.NewReply(clockRef)) case packet.CVRP: cvrpPacket, err := packet.NewSyncCvrpPacketFromBytes(data) @@ -328,7 +337,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte videoClockRef := valeria.Local.setupVideoClock(cvrpPacket.DeviceClockRef) log.Debugf("Send CVRP-RPLY {correlation:%x, clockRef:%x}", cvrpPacket.CorrelationID, videoClockRef) - return adapter.WriteDataToUsb(cvrpPacket.NewReply(videoClockRef)) + return valeria.Remote.usbAdapter.WriteDataToUsb(cvrpPacket.NewReply(videoClockRef)) case packet.CLOK: clokPacket, err := packet.NewSyncClokPacketFromBytes(data) if err != nil { @@ -338,7 +347,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte clockRef := valeria.Local.setupMainClock(clokPacket.ClockRef) log.Debugf("Send CLOK-RPLY {correlation:%x, clockRef:%x}", clokPacket.CorrelationID, clockRef) - return adapter.WriteDataToUsb(clokPacket.NewReply(clockRef)) + return valeria.Remote.usbAdapter.WriteDataToUsb(clokPacket.NewReply(clockRef)) case packet.TIME: timePacket, err := packet.NewSyncTimePacketFromBytes(data) if err != nil { @@ -351,7 +360,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte return fmt.Errorf("could not create SYNC TIME REPLY") } log.Debugf("Send TIME-REPLY {correlation:%x, time:%s}", timePacket.CorrelationID, timeToSend) - return adapter.WriteDataToUsb(replyBytes) + return valeria.Remote.usbAdapter.WriteDataToUsb(replyBytes) //TODO: turn into nice API function case packet.AFMT: afmtPacket, err := packet.NewSyncAfmtPacketFromBytes(data) @@ -362,7 +371,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte replyBytes := afmtPacket.NewReply() log.Debugf("Send AFMT-REPLY {correlation:%x}", afmtPacket.CorrelationID) - return adapter.WriteDataToUsb(replyBytes) + return valeria.Remote.usbAdapter.WriteDataToUsb(replyBytes) case packet.SKEW: skewPacket, err := packet.NewSyncSkewPacketFromBytes(data) if err != nil { @@ -370,7 +379,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte } skewValue := valeria.Local.skew() log.Debugf("Rcv:%s Reply:%f", skewPacket.String(), skewValue) - return adapter.WriteDataToUsb(skewPacket.NewReply(skewValue)) + return valeria.Remote.usbAdapter.WriteDataToUsb(skewPacket.NewReply(skewValue)) case packet.STOP: stopPacket, err := packet.NewSyncStopPacketFromBytes(data) if err != nil { @@ -378,7 +387,7 @@ func handleSyncPacket(data []byte, valeria *ValeriaInterface, adapter *UsbAdapte } valeria.Local.stop() log.Debugf("Rcv:%s", stopPacket.String()) - return adapter.WriteDataToUsb(stopPacket.NewReply()) + return valeria.Remote.usbAdapter.WriteDataToUsb(stopPacket.NewReply()) default: return fmt.Errorf("received unknown sync packet type: %x", data) } From 2ea3c604b82f50afa79e2970625a514f3dbbf294 Mon Sep 17 00:00:00 2001 From: Daniel Paulus Date: Sun, 21 Nov 2021 21:35:28 +0100 Subject: [PATCH 3/5] wire new code up --- main.go | 187 ++---------------------------- screencapture/utils.go | 130 +++++++++++++++++++++ screencapture/valeriainterface.go | 1 + 3 files changed, 138 insertions(+), 180 deletions(-) create mode 100644 screencapture/utils.go diff --git a/main.go b/main.go index ea51703..ad0b830 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "fmt" stdlog "log" "os" - "os/signal" "strings" "time" @@ -97,12 +96,6 @@ The commands work as following: return } - testcmd, _ := arguments.Bool("test") - if testcmd { - test(device) - return - } - audioCommand, _ := arguments.Bool("audio") if audioCommand { outfile, err := arguments.String("") @@ -234,7 +227,7 @@ func recordAudioGst(outfile string, device screencapture.IosDevice, audiotype st printErrJSON(err, "Failed creating custom pipeline") return } - startWithConsumer(gStreamer, device, true) + screencapture.StartWithConsumer(gStreamer, device, true) } func runDiagnostics(outfile string, dump bool, dumpFile string, device screencapture.IosDevice) { @@ -246,10 +239,10 @@ func runDiagnostics(outfile string, dump bool, dumpFile string, device screencap defer metricsFile.Close() consumer := diagnostics.NewDiagnosticsConsumer(metricsFile, time.Second*10) if dump { - startWithConsumerDump(consumer, device, dumpFile) + screencapture.StartWithConsumerDump(consumer, device, dumpFile) return } - startWithConsumer(consumer, device, false) + screencapture.StartWithConsumer(consumer, device, false) } func recordAudioWav(outfile string, device screencapture.IosDevice) { @@ -276,7 +269,7 @@ func recordAudioWav(outfile string, device screencapture.IosDevice) { } }() - startWithConsumer(wavFileWriter, device, true) + screencapture.StartWithConsumer(wavFileWriter, device, true) } func startGStreamerWithCustomPipeline(device screencapture.IosDevice, pipelineString string) { @@ -286,13 +279,13 @@ func startGStreamerWithCustomPipeline(device screencapture.IosDevice, pipelineSt printErrJSON(err, "Failed creating custom pipeline") return } - startWithConsumer(gStreamer, device, false) + screencapture.StartWithConsumer(gStreamer, device, false) } func startGStreamer(device screencapture.IosDevice) { log.Debug("Starting Gstreamer") gStreamer := gstadapter.New() - startWithConsumer(gStreamer, device, false) + screencapture.StartWithConsumer(gStreamer, device, false) } // Just dump a list of what was discovered to the console @@ -326,102 +319,6 @@ func activate(device screencapture.IosDevice) { }) } -func test(device screencapture.IosDevice) { - log.SetLevel(log.DebugLevel) - var err error - device, err = screencapture.EnableQTConfig(device) - if err != nil { - printErrJSON(err, "Error enabling QT config") - return - } - - usbAdapter := &screencapture.UsbAdapterNew{} - err = usbAdapter.InitializeUSB(device) - if err != nil { - printErrJSON(err, "failed initializing usb with error") - return - } - - valeriaInterface := screencapture.NewValeriaInterface(usbAdapter) - defer CloseAll(usbAdapter, valeriaInterface) - go func() { - err := valeriaInterface.StartReadLoop() - log.Info("Valeria read loop stopped.") - if err != nil { - log.Errorf("Valeria read loop stopped with error %v", err) - } - }() - - err = valeriaInterface.Local.AwaitPing() - if err != nil { - log.Errorf("ping timed out failed", err) - return - } - log.Info("Ping received, responding..") - - err = valeriaInterface.Remote.Ping() - fatalIfErr(err, "failed sending Ping") - log.Info("Handshake complete, awaiting audio clock sync") - err = valeriaInterface.Local.AwaitAudioClockSync() - if err != nil { - log.Fatalf("audio clock sync failed", err) - } - log.Info("audio clock sync ok, enabling video") - err = valeriaInterface.Remote.EnableVideo() - fatalIfErr(err, "failed enabling video") - log.Infof("enabling audio") - err = valeriaInterface.Remote.EnableAudio() - fatalIfErr(err, "failed enabling audio") - log.Info("awaiting video clock sync") - err = valeriaInterface.Local.AwaitVideoClockSync() - fatalIfErr(err, "failed waiting for video clock sync") - log.Info("sending initial sample data request") - err = valeriaInterface.Remote.RequestSampleData() - fatalIfErr(err, "failed requesting sample data") - go func() { - for { - buf := valeriaInterface.Local.ReadSampleBuffer() - err := valeriaInterface.Remote.RequestSampleData() - if err != nil { - log.Fatalf("failed sending need") - } - log.Infof(buf.String()) - } - }() - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c -} - -func CloseAll(usbAdapter *screencapture.UsbAdapterNew, valeriaInterface screencapture.ValeriaInterface) { - log.Info("stopping audio") - err := valeriaInterface.Remote.StopAudio() - if err != nil { - log.Errorf("error stopping audio", err) - } - - log.Info("stopping video") - err = valeriaInterface.Remote.StopVideo() - if err != nil { - log.Errorf("error stopping video", err) - } - log.Info("awaiting audio release") - err = valeriaInterface.Local.AwaitAudioClockRelease() - if err != nil { - log.Errorf("error waiting audio clock release", err) - } - - log.Info("awaiting video release") - err = valeriaInterface.Local.AwaitVideoClockRelease() - if err != nil { - log.Errorf("error waiting video clock release", err) - } - - log.Info("shutting down usbadapter") - err = usbAdapter.Close() - log.Info("Stream closed successfullly, good bye :-)") -} - func record(h264FilePath string, wavFilePath string, device screencapture.IosDevice) { log.Debugf("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath) @@ -457,72 +354,7 @@ func record(h264FilePath string, wavFilePath string, device screencapture.IosDev } }() - startWithConsumer(writer, device, false) -} - -func startWithConsumer(consumer screencapture.CmSampleBufConsumer, device screencapture.IosDevice, audioOnly bool) { - var err error - device, err = screencapture.EnableQTConfig(device) - if err != nil { - printErrJSON(err, "Error enabling QT config") - return - } - - adapter := screencapture.UsbAdapter{} - stopSignal := make(chan interface{}) - waitForSigInt(stopSignal) - - mp := screencapture.NewMessageProcessor(&adapter, stopSignal, consumer, audioOnly) - - err = adapter.StartReading(device, &mp, stopSignal) - consumer.Stop() - if err != nil { - printErrJSON(err, "failed connecting to usb") - } -} - -func startWithConsumerDump(consumer screencapture.CmSampleBufConsumer, device screencapture.IosDevice, dumpPath string) { - var err error - device, err = screencapture.EnableQTConfig(device) - if err != nil { - printErrJSON(err, "Error enabling QT config") - return - } - - inboundMessagesFile, err := os.Create("inbound-" + dumpPath) - if err != nil { - log.Fatalf("Could not open file: %v", err) - } - defer inboundMessagesFile.Close() - outboundMessagesFile, err := os.Create("outbound-" + dumpPath) - if err != nil { - log.Fatalf("Could not open file: %v", err) - } - defer outboundMessagesFile.Close() - log.Debug("Start dumping all binary transfer") - adapter := screencapture.UsbAdapter{Dump: true, DumpInWriter: inboundMessagesFile, DumpOutWriter: outboundMessagesFile} - stopSignal := make(chan interface{}) - waitForSigInt(stopSignal) - - mp := screencapture.NewMessageProcessor(&adapter, stopSignal, consumer, false) - - err = adapter.StartReading(device, &mp, stopSignal) - consumer.Stop() - if err != nil { - printErrJSON(err, "failed connecting to usb") - } -} - -func waitForSigInt(stopSignalChannel chan interface{}) { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - log.Debugf("Signal received: %s", sig) - var stopSignal interface{} - stopSignalChannel <- stopSignal - } - }() + screencapture.StartWithConsumer(writer, device, false) } func checkDeviceIsPaired(device screencapture.IosDevice) { @@ -539,11 +371,6 @@ func checkDeviceIsPaired(device screencapture.IosDevice) { log.Infof("found %s %s for udid %s", allValues["DeviceName"], allValues["ProductVersion"], dev.Properties.SerialNumber) } -func fatalIfErr(err error, msg string) { - if err != nil { - log.Errorf("%s: %v", msg, err) - } -} func printErrJSON(err error, msg string) { printJSON(map[string]interface{}{ "original_error": err.Error(), diff --git a/screencapture/utils.go b/screencapture/utils.go new file mode 100644 index 0000000..cb5adc1 --- /dev/null +++ b/screencapture/utils.go @@ -0,0 +1,130 @@ +package screencapture + +import ( + log "github.com/sirupsen/logrus" + "os" + "os/signal" +) + +func StartWithConsumer(consumer CmSampleBufConsumer, device IosDevice, audioOnly bool) { + var err error + device, err = EnableQTConfig(device) + if err != nil { + log.Fatalf("error enabling QT config %v for device %v", err, device) + } + + usbAdapter := &UsbAdapterNew{} + err = usbAdapter.InitializeUSB(device) + if err != nil { + log.Fatalf("failed initializing usb with error %v for device %v", err, device) + } + + valeriaInterface := NewValeriaInterface(usbAdapter) + defer CloseAll(usbAdapter, valeriaInterface) + go func() { + err := valeriaInterface.StartReadLoop() + log.Info("Valeria read loop stopped.") + if err != nil { + log.Errorf("Valeria read loop stopped with error %v", err) + } + }() + setupSession(valeriaInterface) + + go func() { + for { + buf := valeriaInterface.Local.ReadSampleBuffer() + err := valeriaInterface.Remote.RequestSampleData() + if err != nil { + log.Debug("failed sending need") + return + } + err = consumer.Consume(buf) + if err != nil { + log.Warnf("consumer %v failed to consume buffer %v with error %v", consumer, buf, err) + } + } + }() + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c +} +func StartWithConsumerDump(consumer CmSampleBufConsumer, device IosDevice, dumpPath string){} + +func setupSession(valeriaInterface ValeriaInterface) { + err := valeriaInterface.Local.AwaitPing() + if err != nil { + log.Errorf("ping timed out failed %v", err) + return + } + + log.Info("ping received, responding..") + err = valeriaInterface.Remote.Ping() + if err != nil { + log.Errorf("failed sending Ping %v", err) + return + } + + log.Info("handshake complete, awaiting audio clock sync") + err = valeriaInterface.Local.AwaitAudioClockSync() + if err != nil { + log.Errorf("audio clock sync failed %v", err) + return + } + + log.Info("audio clock sync ok, enabling video") + err = valeriaInterface.Remote.EnableVideo() + if err != nil { + log.Errorf("failed enabling video %v", err) + return + } + + log.Infof("enabling audio") + err = valeriaInterface.Remote.EnableAudio() + if err != nil { + log.Errorf("failed enabling audio %v", err) + return + } + + log.Info("awaiting video clock sync") + err = valeriaInterface.Local.AwaitVideoClockSync() + if err != nil { + log.Errorf("failed waiting for video clock sync %v", err) + return + } + + log.Info("sending initial sample data request") + err = valeriaInterface.Remote.RequestSampleData() + if err != nil { + log.Errorf("failed requesting sample data %v", err) + return + } +} + +func CloseAll(usbAdapter *UsbAdapterNew, valeriaInterface ValeriaInterface) { + log.Info("stopping audio") + err := valeriaInterface.Remote.StopAudio() + if err != nil { + log.Errorf("error stopping audio", err) + } + + log.Info("stopping video") + err = valeriaInterface.Remote.StopVideo() + if err != nil { + log.Errorf("error stopping video", err) + } + log.Info("awaiting audio release") + err = valeriaInterface.Local.AwaitAudioClockRelease() + if err != nil { + log.Errorf("error waiting audio clock release", err) + } + + log.Info("awaiting video release") + err = valeriaInterface.Local.AwaitVideoClockRelease() + if err != nil { + log.Errorf("error waiting video clock release", err) + } + + log.Info("shutting down usbadapter") + err = usbAdapter.Close() + log.Info("Stream closed successfullly, good bye :-)") +} diff --git a/screencapture/valeriainterface.go b/screencapture/valeriainterface.go index b1d243a..948ac7b 100644 --- a/screencapture/valeriainterface.go +++ b/screencapture/valeriainterface.go @@ -166,6 +166,7 @@ func (l LocalValeriaApi) skew() float64 { l.dataHolder.lastEatFrameReceivedDeviceAudioClockTime) } +//TODO: make this closeable to prevent goroutine leaks //ReadSampleBuffer blocks until a buffer is received or the interface is closed func (l LocalValeriaApi) ReadSampleBuffer() coremedia.CMSampleBuffer { return <-l.sampleDataChannel From 19f7cceb2a6d5fb14029826abd7e910c68e4b58b Mon Sep 17 00:00:00 2001 From: Daniel Paulus Date: Mon, 22 Nov 2021 20:54:36 +0100 Subject: [PATCH 4/5] add a dummy example --- screencapture/gstadapter/gst_adapter.go | 12 ++++--- screencapture/usbadapter-new.go | 5 ++- screencapture/utils.go | 48 +++++++++++++++++++++---- screencapture/valeriainterface.go | 12 +++++-- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/screencapture/gstadapter/gst_adapter.go b/screencapture/gstadapter/gst_adapter.go index 88c71c6..411af7d 100644 --- a/screencapture/gstadapter/gst_adapter.go +++ b/screencapture/gstadapter/gst_adapter.go @@ -3,13 +3,12 @@ package gstadapter import ( "encoding/binary" "fmt" - "os" - "runtime" - "github.com/danielpaulus/gst" "github.com/danielpaulus/quicktime_video_hack/screencapture/coremedia" "github.com/lijo-jose/glib" log "github.com/sirupsen/logrus" + "os" + "runtime" ) //GstAdapter contains the AppSrc for accessing Gstreamer. @@ -36,11 +35,12 @@ func New() *GstAdapter { audioAppSrc := setUpAudioPipelineBase(pl) setupLivePlayAudio(pl) - pl.SetState(gst.STATE_PLAYING) runGlibMainLoop() + pl.SetState(gst.STATE_PLAYING) + log.Info("Gstreamer is running!") - gsta := GstAdapter{videoAppSrc: videoAppSrc, audioAppSrc: audioAppSrc, firstAudioSample: true} + gsta := GstAdapter{videoAppSrc: videoAppSrc, audioAppSrc: audioAppSrc, firstAudioSample: true, pipeline: pl} return &gsta } @@ -220,6 +220,8 @@ func checkElem(e *gst.Element, name string) { //Consume will transfer AV data into a Gstreamer AppSrc func (gsta *GstAdapter) Consume(buf coremedia.CMSampleBuffer) error { + s, _, _:=gsta.pipeline.GetState(200) + log.Info(s.String()) if buf.MediaType == coremedia.MediaTypeSound { if gsta.firstAudioSample { gsta.firstAudioSample = false diff --git a/screencapture/usbadapter-new.go b/screencapture/usbadapter-new.go index 3a656ce..c65af4d 100644 --- a/screencapture/usbadapter-new.go +++ b/screencapture/usbadapter-new.go @@ -1,9 +1,11 @@ package screencapture import ( + "context" "encoding/binary" "fmt" "io" + "time" "github.com/pkg/errors" @@ -26,7 +28,8 @@ type UsbAdapterNew struct { //WriteDataToUsb implements the UsbWriter interface and sends the byte array to the usb bulk endpoint. func (usbAdapter *UsbAdapterNew) WriteDataToUsb(bytes []byte) error { - _, err := usbAdapter.outEndpoint.Write(bytes) + ctx, _ := context.WithTimeout(context.Background(), time.Second*2) + _, err := usbAdapter.outEndpoint.WriteContext(ctx, bytes) if err != nil { return err } diff --git a/screencapture/utils.go b/screencapture/utils.go index cb5adc1..7b7b649 100644 --- a/screencapture/utils.go +++ b/screencapture/utils.go @@ -1,9 +1,13 @@ package screencapture import ( + "bufio" + "fmt" + "github.com/danielpaulus/quicktime_video_hack/screencapture/coremedia" log "github.com/sirupsen/logrus" "os" "os/signal" + "time" ) func StartWithConsumer(consumer CmSampleBufConsumer, device IosDevice, audioOnly bool) { @@ -33,22 +37,54 @@ func StartWithConsumer(consumer CmSampleBufConsumer, device IosDevice, audioOnly go func() { for { buf := valeriaInterface.Local.ReadSampleBuffer() - err := valeriaInterface.Remote.RequestSampleData() - if err != nil { - log.Debug("failed sending need") - return - } + go func() { + err := valeriaInterface.Remote.RequestSampleData() + if err != nil { + log.Debug("failed sending need") + return + } + }() err = consumer.Consume(buf) if err != nil { log.Warnf("consumer %v failed to consume buffer %v with error %v", consumer, buf, err) } } }() + + log.Info("wait") + time.Sleep(time.Second * 5) + log.Info("pause") + valeriaInterface.Remote.StopVideo() + valeriaInterface.Remote.StopAudio() + time.Sleep(time.Second*5) + consumer = newWriter("bla%s.h264") + log.Info("re enable") + valeriaInterface.Remote.EnableAudio() + valeriaInterface.Remote.EnableVideo() + log.Info("ok") + c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c } -func StartWithConsumerDump(consumer CmSampleBufConsumer, device IosDevice, dumpPath string){} +func StartWithConsumerDump(consumer CmSampleBufConsumer, device IosDevice, dumpPath string) {} + +func newWriter(basePath string) coremedia.AVFileWriter { + h264FilePath := fmt.Sprintf(basePath, "-3") + wavFilePath := fmt.Sprintf(basePath, "-3") + h264File, err := os.Create(h264FilePath) + if err != nil { + log.Debugf("Error creating h264File:%s", err) + log.Errorf("Could not open h264File '%s'", h264FilePath) + } + wavFile, err := os.Create(wavFilePath) + if err != nil { + log.Debugf("Error creating wav file:%s", err) + log.Errorf("Could not open wav file '%s'", wavFilePath) + } + + return coremedia.NewAVFileWriter(bufio.NewWriter(h264File), bufio.NewWriter(wavFile)) +} func setupSession(valeriaInterface ValeriaInterface) { err := valeriaInterface.Local.AwaitPing() diff --git a/screencapture/valeriainterface.go b/screencapture/valeriainterface.go index 948ac7b..3875c31 100644 --- a/screencapture/valeriainterface.go +++ b/screencapture/valeriainterface.go @@ -40,6 +40,7 @@ type LocalValeriaApi struct { sampleDataChannel chan coremedia.CMSampleBuffer audioReleaseChannel chan error videoReleaseChannel chan error + consumer CmSampleBufConsumer } type DeviceValeriaAPI struct { @@ -49,6 +50,8 @@ type DeviceValeriaAPI struct { func NewValeriaInterface(usbAdapter UsbWriterNew) ValeriaInterface { dataHolder := DataHolder{} + + remote := DeviceValeriaAPI{dataHolder: dataHolder, usbAdapter: usbAdapter} local := LocalValeriaApi{ pingChannel: make(chan error, 1), audioClockChannel: make(chan error, 1), @@ -56,9 +59,9 @@ func NewValeriaInterface(usbAdapter UsbWriterNew) ValeriaInterface { audioReleaseChannel: make(chan error, 1), videoReleaseChannel: make(chan error, 1), dataHolder: dataHolder, - sampleDataChannel: make(chan coremedia.CMSampleBuffer, 50), + sampleDataChannel: make(chan coremedia.CMSampleBuffer, 100), + remote: remote, } - remote := DeviceValeriaAPI{dataHolder: dataHolder, usbAdapter: usbAdapter} valeriaIface := ValeriaInterface{Local: local, errorChannel: make(chan error, 1), closeChannel: make(chan interface{}), @@ -185,6 +188,7 @@ func (l LocalValeriaApi) receiveAudioSample(buf coremedia.CMSampleBuffer) { } l.sampleDataChannel <- buf + //l.consumer.Consume(buf) if log.IsLevelEnabled(log.DebugLevel) { l.dataHolder.audioSamplesReceived++ if l.dataHolder.audioSamplesReceived%100 == 0 { @@ -195,6 +199,9 @@ func (l LocalValeriaApi) receiveAudioSample(buf coremedia.CMSampleBuffer) { func (l LocalValeriaApi) feed(buf coremedia.CMSampleBuffer) { l.sampleDataChannel <- buf +//l.consumer.Consume(buf) + + if log.IsLevelEnabled(log.DebugLevel) { l.dataHolder.videoSamplesReceived++ if l.dataHolder.videoSamplesReceived%500 == 0 { @@ -245,6 +252,7 @@ func (d DeviceValeriaAPI) RequestSampleData() error { func (d DeviceValeriaAPI) EnableVideo() error { deviceInfo := packet.NewAsynHpd1Packet(packet.CreateHpd1DeviceInfoDict()) log.Debug("Sending ASYN HPD1") + //TODO: sending once is enough err := d.usbAdapter.WriteDataToUsb(deviceInfo) if err != nil { return err From 43f65bb411aee0d95f5d04f1d00254acf3ac11da Mon Sep 17 00:00:00 2001 From: Daniel Paulus Date: Sat, 27 Nov 2021 13:24:37 +0100 Subject: [PATCH 5/5] add hacky version --- go.mod | 2 +- go.sum | 2 ++ main.go | 33 ++++----------------- screencapture/utils.go | 65 ++++++++++++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index 44bfa4d..d5b91d4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/danielpaulus/go-ios v1.0.13 github.com/danielpaulus/gst v0.0.0-20200201205042-e6d2974fceb8 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 - github.com/google/gousb v2.1.0+incompatible + github.com/google/gousb v1.1.0 github.com/lijo-jose/glib v0.0.0-20191012030101-93ee72d7d646 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index f5ea51a..c2ede9d 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/google/gousb v1.1.0 h1:s/970WE1z968MC+dtWbuxDHCcx9kwANQo6UcZtfTfx0= +github.com/google/gousb v1.1.0/go.mod h1:Tl4HdAs1ThE3gECkNwz+1MWicX6FXddhJEw7L8jRDiI= github.com/google/gousb v2.1.0+incompatible h1:ApzMDjF3FeO219QwWybJxYfFhXQzPLOEy0o+w9k5DNI= github.com/google/gousb v2.1.0+incompatible/go.mod h1:Tl4HdAs1ThE3gECkNwz+1MWicX6FXddhJEw7L8jRDiI= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= diff --git a/main.go b/main.go index ad0b830..49ac941 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "encoding/json" "fmt" stdlog "log" @@ -26,7 +25,7 @@ func main() { Usage: qvh devices [-v] qvh activate [--udid=] [-v] - qvh record [--udid=] [-v] + qvh record [--udid=] [-v] qvh audio (--mp3 | --ogg | --wav) [--udid=] [-v] qvh gstreamer [--pipeline=] [--examples] [--udid=] [-v] qvh diagnostics [--dump=] [--udid=] @@ -144,17 +143,8 @@ The commands work as following: recordCommand, _ := arguments.Bool("record") if recordCommand { - h264FilePath, err := arguments.String("") - if err != nil { - printErrJSON(err, "Missing parameter. Please specify a valid path like '/home/me/out.h264'") - return - } - waveFilePath, err := arguments.String("") - if err != nil { - printErrJSON(err, "Missing parameter. Please specify a valid path like '/home/me/out.raw'") - return - } - record(h264FilePath, waveFilePath, device) + + record("", "", device) } gstreamerCommand, _ := arguments.Bool("gstreamer") if gstreamerCommand { @@ -322,19 +312,8 @@ func activate(device screencapture.IosDevice) { func record(h264FilePath string, wavFilePath string, device screencapture.IosDevice) { log.Debugf("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath) - h264File, err := os.Create(h264FilePath) - if err != nil { - log.Debugf("Error creating h264File:%s", err) - log.Errorf("Could not open h264File '%s'", h264FilePath) - } - wavFile, err := os.Create(wavFilePath) - if err != nil { - log.Debugf("Error creating wav file:%s", err) - log.Errorf("Could not open wav file '%s'", wavFilePath) - } - - writer := coremedia.NewAVFileWriter(bufio.NewWriter(h264File), bufio.NewWriter(wavFile)) +/* defer func() { stat, err := wavFile.Stat() if err != nil { @@ -353,8 +332,8 @@ func record(h264FilePath string, wavFilePath string, device screencapture.IosDev log.Fatalf("Error closing h264File '%s'. %s", h264FilePath, err.Error()) } - }() - screencapture.StartWithConsumer(writer, device, false) + }()*/ + screencapture.StartWithConsumer(nil, device, false) } func checkDeviceIsPaired(device screencapture.IosDevice) { diff --git a/screencapture/utils.go b/screencapture/utils.go index 7b7b649..5be7880 100644 --- a/screencapture/utils.go +++ b/screencapture/utils.go @@ -7,10 +7,13 @@ import ( log "github.com/sirupsen/logrus" "os" "os/signal" - "time" + "syscall" ) +var writerCount = 0 + func StartWithConsumer(consumer CmSampleBufConsumer, device IosDevice, audioOnly bool) { + consumer, vidfile, audiofile := newWriter() var err error device, err = EnableQTConfig(device) if err != nil { @@ -51,27 +54,51 @@ func StartWithConsumer(consumer CmSampleBufConsumer, device IosDevice, audioOnly } }() - log.Info("wait") - time.Sleep(time.Second * 5) - log.Info("pause") - valeriaInterface.Remote.StopVideo() - valeriaInterface.Remote.StopAudio() - time.Sleep(time.Second*5) - consumer = newWriter("bla%s.h264") - log.Info("re enable") - valeriaInterface.Remote.EnableAudio() - valeriaInterface.Remote.EnableVideo() - log.Info("ok") - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c + signal.Notify(c, syscall.SIGINT) + signal.Notify(c, syscall.SIGUSR1) + signal.Notify(c, syscall.SIGUSR2) + signal.Notify(c, syscall.SIGTERM) + for { + + sig := <-c + switch sig { + case syscall.SIGUSR1: + //pause + log.Info("pause") + valeriaInterface.Remote.StopVideo() + valeriaInterface.Remote.StopAudio() + vidfile.Close() + stat, err := audiofile.Stat() + if err != nil { + log.Fatal("Could not get wav file stats", err) + } + err = coremedia.WriteWavHeader(int(stat.Size()), audiofile) + if err != nil { + log.Fatalf("Error writing wave header %s might be invalid. %s", audiofile, err.Error()) + } + err = audiofile.Close() + if err != nil { + log.Fatalf("Error closing wave file. '%s' might be invalid. %s", audiofile, err.Error()) + } + case syscall.SIGUSR2: + //resume + consumer, vidfile, audiofile = newWriter() + log.Info("resume") + valeriaInterface.Remote.EnableAudio() + valeriaInterface.Remote.EnableVideo() + default: + return + } + } } + func StartWithConsumerDump(consumer CmSampleBufConsumer, device IosDevice, dumpPath string) {} -func newWriter(basePath string) coremedia.AVFileWriter { - h264FilePath := fmt.Sprintf(basePath, "-3") - wavFilePath := fmt.Sprintf(basePath, "-3") +func newWriter() (coremedia.AVFileWriter, *os.File, *os.File) { + h264FilePath := fmt.Sprintf("video-%03d.h264", writerCount) + wavFilePath := fmt.Sprintf("audio-%03d.wav", writerCount) + writerCount++ h264File, err := os.Create(h264FilePath) if err != nil { log.Debugf("Error creating h264File:%s", err) @@ -83,7 +110,7 @@ func newWriter(basePath string) coremedia.AVFileWriter { log.Errorf("Could not open wav file '%s'", wavFilePath) } - return coremedia.NewAVFileWriter(bufio.NewWriter(h264File), bufio.NewWriter(wavFile)) + return coremedia.NewAVFileWriter(bufio.NewWriter(h264File), bufio.NewWriter(wavFile)), h264File, wavFile } func setupSession(valeriaInterface ValeriaInterface) {