Skip to content

Commit 5729f47

Browse files
authored
Merge pull request #28 from danielpaulus/addAiffFileSupport
Add wav file support
2 parents 22c9394 + 6c036d6 commit 5729f47

File tree

9 files changed

+357
-91
lines changed

9 files changed

+357
-91
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ So if you are just interested in the protocol or if you want to implement this i
1818
run `go run main.go --help` to see how it works
1919

2020
Progress:
21-
1. ~~Make the `go run main.go dumpraw` work on the first execution (currently you have to run it twice and it will start recording on the second run)~~
22-
2. FIX: After running the dumpraw command and saving a video, you have to unplug the device to record another video currently
21+
1. ~~Make the `go run main.go record` work on the first execution (currently you have to run it twice and it will start recording on the second run)~~
22+
2. FIX: After running the record command and saving a video, you have to unplug the device to record another video currently
2323
3. Make a release :-D
2424
4. Generate GStreamer compatible x264 stream probably by wrapping the NaLus in RTP headers
2525
5. ~~Complete packet documentation~~
@@ -39,7 +39,7 @@ Extra Goals:
3939

4040
2. What does not work
4141

42-
This might be wrong, needs investigation--> `qvh dumpraw` won't work on MAC OS because the binary needs to be codesigned with `com.apple.ibridge.control`
42+
This might be wrong, needs investigation--> `qvh record` won't work on MAC OS because the binary needs to be codesigned with `com.apple.ibridge.control`
4343
apparently that is a protected Entitlement that I have no idea how to use or sign my binary with.
4444

4545
2. Make sure to use either this fork `https://github.com/GroundControl-Solutions/libusb`

log/core_media_research.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@ cmTime.Value = 88200;
2020
https://github.com/xamarin/xamarin-macios/tree/master/src/CoreMedia
2121

2222
### Core Media Headers
23-
https://github.com/phracker/MacOSX-SDKs/tree/master/MacOSX10.9.sdk/System/Library/Frameworks/CoreMedia.framework
23+
https://github.com/phracker/MacOSX-SDKs/tree/master/MacOSX10.9.sdk/System/Library/Frameworks/CoreMedia.framework
24+
25+
### what does packed mean
26+
https://developer.apple.com/library/archive/documentation/MusicAudio/Reference/CAFSpec/CAF_spec/CAF_spec.html#//apple_ref/doc/uid/TP40001862-CH210-BCGJEBBI

main.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package main
22

33
import (
44
"bufio"
5+
"os"
6+
"os/signal"
7+
58
"github.com/danielpaulus/quicktime_video_hack/screencapture"
69
"github.com/danielpaulus/quicktime_video_hack/screencapture/coremedia"
710
"github.com/docopt/docopt-go"
811
log "github.com/sirupsen/logrus"
9-
"os"
10-
"os/signal"
1112
)
1213

1314
func main() {
@@ -17,7 +18,7 @@ func main() {
1718
Usage:
1819
qvh devices
1920
qvh activate
20-
qvh dumpraw <outfile>
21+
qvh record <h264file> <wavfile>
2122
2223
Options:
2324
-h --help Show this screen.
@@ -28,8 +29,9 @@ Options:
2829
The commands work as following:
2930
devices lists iOS devices attached to this host and tells you if video streaming was activated for them
3031
activate only enables the video streaming config for the given device
31-
dumpraw will start video recording and dump it to a raw h264 file playable by VLC.
32-
Run like: "qvh dumpraw /home/yourname/out.h264"
32+
record will start video&audio recording. Video will be saved in a raw h264 file playable by VLC.
33+
Audio will be saved in a uncompressed wav file.
34+
Run like: "qvh record /home/yourname/out.h264 /home/yourname/out.wav"
3335
`
3436
arguments, _ := docopt.ParseDoc(usage)
3537
//TODO: add verbose switch to conf this
@@ -50,14 +52,19 @@ The commands work as following:
5052
return
5153
}
5254

53-
rawStreamCommand, _ := arguments.Bool("dumpraw")
55+
rawStreamCommand, _ := arguments.Bool("record")
5456
if rawStreamCommand {
55-
outFilePath, err := arguments.String("<outfile>")
57+
h264FilePath, err := arguments.String("<h264file>")
5658
if err != nil {
57-
log.Error("Missing outfile parameter. Please specify a valid path like '/home/me/out.h264'")
59+
log.Error("Missing <h264file> parameter. Please specify a valid path like '/home/me/out.h264'")
5860
return
5961
}
60-
dumpraw(outFilePath)
62+
waveFilePath, err := arguments.String("<wavfile>")
63+
if err != nil {
64+
log.Error("Missing <wavfile> parameter. Please specify a valid path like '/home/me/out.raw'")
65+
return
66+
}
67+
record(h264FilePath, waveFilePath)
6168
}
6269
}
6370

@@ -118,23 +125,49 @@ func activate() {
118125
log.Info(qtOutput)
119126
}
120127

121-
func dumpraw(outFilePath string) {
128+
func record(h264FilePath string, wavFilePath string) {
122129
activate()
123130
cleanup := screencapture.Init()
124131
deviceList, err := screencapture.FindIosDevices()
125132
defer cleanup()
126133
if err != nil {
127134
log.Fatal("Error finding iOS Devices", err)
128135
}
129-
log.Infof("Writing output to:%s", outFilePath)
136+
log.Infof("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath)
130137
dev := deviceList[0]
131138

132-
file, err := os.Create(outFilePath)
139+
h264File, err := os.Create(h264FilePath)
133140
if err != nil {
134-
log.Debugf("Error creating file:%s", err)
135-
log.Errorf("Could not open file '%s'", outFilePath)
141+
log.Debugf("Error creating h264File:%s", err)
142+
log.Errorf("Could not open h264File '%s'", h264FilePath)
136143
}
137-
writer := coremedia.NewNaluFileWriter(bufio.NewWriter(file))
144+
wavFile, err := os.Create(wavFilePath)
145+
if err != nil {
146+
log.Debugf("Error creating wav file:%s", err)
147+
log.Errorf("Could not open wav file '%s'", wavFilePath)
148+
}
149+
150+
writer := coremedia.NewAVFileWriter(bufio.NewWriter(h264File), bufio.NewWriter(wavFile))
151+
152+
defer func() {
153+
stat, err := wavFile.Stat()
154+
if err != nil {
155+
log.Fatal("Could not get wav file stats", err)
156+
}
157+
err = coremedia.WriteWavHeader(int(stat.Size()), wavFile)
158+
if err != nil {
159+
log.Fatalf("Error writing wave header %s might be invalid. %s", wavFilePath, err.Error())
160+
}
161+
err = wavFile.Close()
162+
if err != nil {
163+
log.Fatalf("Error closing wave file. '%s' might be invalid. %s", wavFilePath, err.Error())
164+
}
165+
err = h264File.Close()
166+
if err != nil {
167+
log.Fatalf("Error closing h264File '%s'. %s", h264FilePath, err.Error())
168+
}
169+
170+
}()
138171
adapter := screencapture.UsbAdapter{}
139172
stopSignal := make(chan interface{})
140173
waitForSigInt(stopSignal)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package coremedia
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
)
7+
8+
var startCode = []byte{00, 00, 00, 01}
9+
10+
//AVFileWriter writes nalus into a file using 0x00000001 as a separator (h264 ANNEX B) and raw pcm audio into a wav file
11+
//Note that you will have to call WriteWavHeader() on the audiofile when you are done to write a wav header and get a valid file.
12+
type AVFileWriter struct {
13+
h264FileWriter io.Writer
14+
wavFileWriter io.Writer
15+
outFilePath string
16+
}
17+
18+
//NewAVFileWriter binary writes nalus in annex b format to the given writer and audio buffers into a wav file.
19+
//Note that you will have to call WriteWavHeader() on the audiofile when you are done to write a wav header and get a valid file.
20+
func NewAVFileWriter(h264FileWriter io.Writer, wavFileWriter io.Writer) AVFileWriter {
21+
return AVFileWriter{h264FileWriter: h264FileWriter, wavFileWriter: wavFileWriter}
22+
}
23+
24+
//Consume writes PPS and SPS as well as sample bufs into a annex b .h264 file and audio samples into a wav file
25+
//Note that you will have to call WriteWavHeader() on the audiofile when you are done to write a wav header and get a valid file.
26+
func (avfw AVFileWriter) Consume(buf CMSampleBuffer) error {
27+
if buf.MediaType == MediaTypeSound {
28+
return avfw.consumeAudio(buf)
29+
}
30+
return avfw.consumeVideo(buf)
31+
}
32+
33+
func (avfw AVFileWriter) consumeVideo(buf CMSampleBuffer) error {
34+
if buf.HasFormatDescription {
35+
err := avfw.writeNalu(buf.FormatDescription.PPS)
36+
if err != nil {
37+
return err
38+
}
39+
err = avfw.writeNalu(buf.FormatDescription.SPS)
40+
if err != nil {
41+
return err
42+
}
43+
}
44+
return avfw.writeNalus(buf.SampleData)
45+
}
46+
47+
func (avfw AVFileWriter) writeNalus(bytes []byte) error {
48+
slice := bytes
49+
for len(slice) > 0 {
50+
length := binary.BigEndian.Uint32(slice)
51+
err := avfw.writeNalu(slice[4 : length+4])
52+
if err != nil {
53+
return err
54+
}
55+
slice = slice[length+4:]
56+
}
57+
return nil
58+
}
59+
60+
func (avfw AVFileWriter) writeNalu(naluBytes []byte) error {
61+
_, err := avfw.h264FileWriter.Write(startCode)
62+
if err != nil {
63+
return err
64+
}
65+
_, err = avfw.h264FileWriter.Write(naluBytes)
66+
if err != nil {
67+
return err
68+
}
69+
return nil
70+
}
71+
72+
func (avfw AVFileWriter) consumeAudio(buffer CMSampleBuffer) error {
73+
_, err := avfw.wavFileWriter.Write(buffer.SampleData)
74+
if err != nil {
75+
return err
76+
}
77+
return nil
78+
}

screencapture/coremedia/nalufilewriter_test.go renamed to screencapture/coremedia/avfilewriter_test.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,40 @@ var startCode = []byte{00, 00, 00, 01}
1717
func TestFileWriter(t *testing.T) {
1818
buf := bytes.NewBuffer(make([]byte, 100))
1919
buf.Reset()
20-
nfw := coremedia.NewNaluFileWriter(buf)
21-
err := nfw.Consume(cmSampleBufWithAFewBytes())
20+
avfw := coremedia.NewAVFileWriter(buf, nil)
21+
err := avfw.Consume(cmSampleBufWithAFewBytes())
2222
assert.NoError(t, err)
2323
assert.Equal(t, 6, buf.Len())
2424
assert.Equal(t, []byte{00, 00, 00, 01, 00, 00}, buf.Bytes())
2525
buf.Reset()
26-
err = nfw.Consume(cmSampleBufWithFdscAndAFewBytes())
26+
err = avfw.Consume(cmSampleBufWithFdscAndAFewBytes())
2727
assert.NoError(t, err)
2828
expectedBytes := append(startCode, fakePPS...)
2929
expectedBytes = append(expectedBytes, startCode...)
3030
expectedBytes = append(expectedBytes, fakeSPS...)
3131
expectedBytes = append(expectedBytes, []byte{00, 00, 00, 01, 00, 00}...)
3232
assert.Equal(t, expectedBytes, buf.Bytes())
3333

34-
nfw = coremedia.NewNaluFileWriter(failingWriter{})
35-
err = nfw.Consume(cmSampleBufWithFdscAndAFewBytes())
34+
avfw = coremedia.NewAVFileWriter(failingWriter{}, nil)
35+
err = avfw.Consume(cmSampleBufWithFdscAndAFewBytes())
3636
assert.Error(t, err)
37-
err = nfw.Consume(cmSampleBufWithAFewBytes())
37+
err = avfw.Consume(cmSampleBufWithAFewBytes())
38+
assert.Error(t, err)
39+
}
40+
41+
func TestFileWriterForAudio(t *testing.T) {
42+
buf := bytes.NewBuffer(make([]byte, 100))
43+
buf.Reset()
44+
avfw := coremedia.NewAVFileWriter(nil, buf)
45+
sampleBuffer := cmSampleBufWithFdscAndAFewBytes()
46+
sampleBuffer.MediaType = coremedia.MediaTypeSound
47+
err := avfw.Consume(sampleBuffer)
48+
if assert.NoError(t, err) {
49+
assert.Equal(t, sampleBuffer.SampleData, buf.Bytes())
50+
}
51+
52+
avfw = coremedia.NewAVFileWriter(nil, failingWriter{})
53+
err = avfw.Consume(sampleBuffer)
3854
assert.Error(t, err)
3955
}
4056

screencapture/coremedia/nalufilewriter.go

Lines changed: 0 additions & 60 deletions
This file was deleted.

0 commit comments

Comments
 (0)