Skip to content

Commit

Permalink
rpcclient: bugfix: recording on ios
Browse files Browse the repository at this point in the history
  • Loading branch information
doronz88 committed Feb 15, 2022
1 parent ae10089 commit eef5c7a
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 27 deletions.
11 changes: 10 additions & 1 deletion src/rpcclient/rpcclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from rpcclient.network import Network
from rpcclient.processes import Processes
from rpcclient.protocol import protocol_message_t, cmd_type_t, exec_chunk_t, exec_chunk_type_t, UNAME_VERSION_LEN, \
reply_protocol_message_t
reply_protocol_message_t, dummy_block_t
from rpcclient.darwin.structs import pid_t, exitcode_t
from rpcclient.symbol import Symbol
from rpcclient.symbols_jar import SymbolsJar
Expand Down Expand Up @@ -181,6 +181,15 @@ def poke(self, address: int, data: bytes):
})
self._sock.sendall(message)

def get_dummy_block(self) -> Symbol:
""" poke data at given address """
message = protocol_message_t.build({
'cmd_type': cmd_type_t.CMD_GET_DUMMY_BLOCK,
'data': None,
})
self._sock.sendall(message)
return self.symbol(dummy_block_t.parse(self._recvall(8)))

def spawn(self, argv: typing.List[str] = None, envp: typing.List[str] = None, stdin=sys.stdin, stdout=sys.stdout,
tty=False, background=False):
""" spawn a new process and forward its stdin, stdout & stderr """
Expand Down
2 changes: 2 additions & 0 deletions src/rpcclient/rpcclient/darwin/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
kCFNumberDoubleType = 13
kCFNumberCFIndexType = 14
kCFNumberMaxType = 14

AVAudioSessionCategoryOptionDefaultToSpeaker = 0x8
67 changes: 41 additions & 26 deletions src/rpcclient/rpcclient/darwin/media.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import struct

from rpcclient.common import path_to_str
from rpcclient.darwin.consts import AVAudioSessionCategoryOptionDefaultToSpeaker
from rpcclient.exceptions import RpcClientException, BadReturnValueError
from rpcclient.darwin.symbol import DarwinSymbol


class Recorder:
def __init__(self, client, recorder: DarwinSymbol):
def __init__(self, client, session, recorder: DarwinSymbol):
self._client = client
self._session = session
self._recorder = recorder

def release(self):
self._recorder.objc_call('release')

def record(self):
self._session.set_category_play_and_record()
self._session.set_active(True)
self._recorder.objc_call('record')

def pause(self):
self._recorder.objc_call('pause')
self._session.set_active(False)

def stop(self):
self._recorder.objc_call('stop')
self._session.set_active(False)

def delete_recording(self):
if not self._recorder.objc_call('deleteRecording'):
Expand All @@ -30,30 +36,27 @@ def delete_recording(self):
def recording(self) -> bool:
return bool(self._recorder.objc_call('isRecording'))

def __del__(self):
try:
self.release()
except Exception: # noqa: E722
# Best effort.
pass


class Player:
def __init__(self, client, player):
def __init__(self, client, session, player):
self._client = client
self._session = session
self._player = player

def release(self):
self._player.objc_call('release')

def play(self):
self._session.set_category_play_and_record(AVAudioSessionCategoryOptionDefaultToSpeaker)
self._session.set_active(True)
self._player.objc_call('play')

def pause(self):
self._player.objc_call('pause')

def stop(self):
self._player.objc_call('stop')
self._session.set_active(False)

def set_volume(self, value: float):
self._player.objc_call('setVolume:', struct.pack('<f', value))
Expand All @@ -70,12 +73,32 @@ def loops(self) -> int:
def loops(self, value: int):
self._player.objc_call('setNumberOfLoops:', value)

def __del__(self):
try:
self.release()
except Exception: # noqa: E722
# Best effort.
pass

class AudioSession:
def __init__(self, client):
self._client = client

AVAudioSession = self._client.symbols.objc_getClass('AVAudioSession')
self._session = AVAudioSession.objc_call('sharedInstance')

def set_active(self, is_active: bool):
self._session.objc_call('setActive:error:', is_active, 0)

def set_category_play_and_record(self, options=0):
self._session.objc_call('setCategory:withOptions:error:',
self._client.symbols.AVAudioSessionCategoryPlayAndRecord[0], options, 0)
self._session.objc_call('requestRecordPermission:', self._client.get_dummy_block())

def override_output_audio_port(self, port: int):
self._session.objc_call('overrideOutputAudioPort:error:', port, 0)

@property
def other_audio_playing(self) -> bool:
return bool(self._session.objc_call('isOtherAudioPlaying'))

@property
def record_permission(self):
return struct.pack('<I', self._session.objc_call('recordPermission'))[::-1].decode()


class DarwinMedia:
Expand All @@ -85,6 +108,7 @@ def __init__(self, client):
"""
self._client = client
self._load_av_foundation()
self.session = AudioSession(self._client)

def _load_av_foundation(self):
options = [
Expand All @@ -99,11 +123,6 @@ def _load_av_foundation(self):
return
raise RpcClientException('failed to load AVFAudio')

def set_audio_session(self):
AVAudioSession = self._client.symbols.objc_getClass('AVAudioSession')
audio_session = AVAudioSession.objc_call('sharedInstance')
audio_session.objc_call('setCategory:error:', self._client.symbols.AVAudioSessionCategoryPlayAndRecord[0], 0)

@path_to_str('filename')
def get_recorder(self, filename: str) -> Recorder:
NSURL = self._client.symbols.objc_getClass('NSURL')
Expand All @@ -115,21 +134,17 @@ def get_recorder(self, filename: str) -> Recorder:
self._client.symbols.AVSampleRateKey: 8000.0,
})

self.set_audio_session()

AVAudioRecorder = self._client.symbols.objc_getClass('AVAudioRecorder')
recorder = AVAudioRecorder.objc_call('alloc').objc_call('initWithURL:settings:error:', url, settings, 0)

return Recorder(self._client, recorder)
return Recorder(self._client, self.session, recorder)

@path_to_str('filename')
def get_player(self, filename: str) -> Player:
NSURL = self._client.symbols.objc_getClass('NSURL')
url = NSURL.objc_call('fileURLWithPath:', self._client.cf(filename))

self.set_audio_session()

AVAudioPlayer = self._client.symbols.objc_getClass('AVAudioPlayer')
player = AVAudioPlayer.objc_call('alloc').objc_call('initWithContentsOfURL:error:', url, 0)

return Player(self._client, player)
return Player(self._client, self.session, player)
3 changes: 3 additions & 0 deletions src/rpcclient/rpcclient/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
CMD_POKE=6,
CMD_REPLY_ERROR=7,
CMD_REPLY_PEEK=8,
CMD_GET_DUMMY_BLOCK=9,
)
DEFAULT_PORT = 5910
MAGIC = 0x12345678
Expand Down Expand Up @@ -80,3 +81,5 @@
'chunk_type' / exec_chunk_type_t,
'size' / Int32ul,
)

dummy_block_t = Int64ul
29 changes: 29 additions & 0 deletions src/rpcserver/rpcserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ typedef enum
CMD_POKE = 6,
CMD_REPLY_ERROR = 7,
CMD_REPLY_PEEK = 8,
CMD_GET_DUMMY_BLOCK = 9,
} cmd_type_t;

typedef enum
Expand Down Expand Up @@ -537,6 +538,29 @@ bool handle_poke(int sockfd)
return result;
}

#if __APPLE__

void (^dummy_block)(void) = ^{
};

bool handle_get_dummy_block(int sockfd)
{
TRACE("enter");
CHECK(sendall(sockfd, (const char *)&dummy_block, sizeof(&dummy_block)));
return true;
error:
return false;
}

#else // !__APPLE__

bool handle_get_dummy_block(int sockfd)
{
return true;
}

#endif // __APPLE__

void handle_client(int sockfd)
{
TRACE("enter. fd: %d", sockfd);
Expand Down Expand Up @@ -587,6 +611,11 @@ void handle_client(int sockfd)
handle_poke(sockfd);
break;
}
case CMD_GET_DUMMY_BLOCK:
{
handle_get_dummy_block(sockfd);
break;
}
default:
{
TRACE("unknown cmd");
Expand Down

0 comments on commit eef5c7a

Please sign in to comment.