mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-06-08 02:53:56 +02:00
Update Ghostgram features
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "WatchBridgeAudio",
|
||||
module_name = "WatchBridgeAudio",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/WatchBridgeAudio/Impl:WatchBridgeAudioImpl",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
objc_library(
|
||||
name = "WatchBridgeAudioImpl",
|
||||
enable_modules = True,
|
||||
module_name = "WatchBridgeAudioImpl",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.mm",
|
||||
"Sources/**/*.h",
|
||||
], allow_empty=True),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SSignalKit:SSignalKit",
|
||||
"//submodules/OpusBinding:OpusBinding",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface TGBridgeAudioDecoder : NSObject
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url outputUrl:(NSURL *)outputURL;
|
||||
- (void)startWithCompletion:(void (^)(void))completion;
|
||||
|
||||
@end
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface TGBridgeAudioEncoder : NSObject
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url;
|
||||
- (void)startWithCompletion:(void (^)(NSString *, int32_t))completion;
|
||||
|
||||
@end
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <WatchBridgeAudioImpl/TGBridgeAudioEncoder.h>
|
||||
#import <WatchBridgeAudioImpl/TGBridgeAudioDecoder.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
#import <WatchBridgeAudioImpl/TGBridgeAudioDecoder.h>
|
||||
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import <SSignalKit/SSignalKit.h>
|
||||
|
||||
#import <OpusBinding/OpusBinding.h>
|
||||
|
||||
const NSInteger TGBridgeAudioDecoderInputSampleRate = 48000;
|
||||
const NSInteger TGBridgeAudioDecoderResultSampleRate = 24000;
|
||||
const NSUInteger TGBridgeAudioDecoderBufferSize = 32768;
|
||||
|
||||
#define checkResult(result,operation) (_checkResultLite((result),(operation),__FILE__,__LINE__))
|
||||
|
||||
struct TGAudioBuffer
|
||||
{
|
||||
NSUInteger capacity;
|
||||
uint8_t *data;
|
||||
NSUInteger size;
|
||||
int64_t pcmOffset;
|
||||
};
|
||||
|
||||
inline TGAudioBuffer *TGAudioBufferWithCapacity(NSUInteger capacity)
|
||||
{
|
||||
TGAudioBuffer *audioBuffer = (TGAudioBuffer *)malloc(sizeof(TGAudioBuffer));
|
||||
audioBuffer->capacity = capacity;
|
||||
audioBuffer->data = (uint8_t *)malloc(capacity);
|
||||
audioBuffer->size = 0;
|
||||
audioBuffer->pcmOffset = 0;
|
||||
return audioBuffer;
|
||||
}
|
||||
|
||||
inline void TGAudioBufferDispose(TGAudioBuffer *audioBuffer)
|
||||
{
|
||||
if (audioBuffer != NULL)
|
||||
{
|
||||
free(audioBuffer->data);
|
||||
free(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool _checkResultLite(OSStatus result, const char *operation, const char* file, int line)
|
||||
{
|
||||
if ( result != noErr )
|
||||
{
|
||||
NSLog(@"%s:%d: %s result %d %08X %4.4s\n", file, line, operation, (int)result, (int)result, (char*)&result);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@interface TGBridgeAudioDecoder ()
|
||||
{
|
||||
NSURL *_url;
|
||||
NSURL *_resultURL;
|
||||
|
||||
OggOpusReader *_opusReader;
|
||||
|
||||
bool _finished;
|
||||
bool _cancelled;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGBridgeAudioDecoder
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url outputUrl:(NSURL *)outputUrl
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_url = url;
|
||||
|
||||
int64_t randomId = 0;
|
||||
arc4random_buf(&randomId, 8);
|
||||
_resultURL = outputUrl;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startWithCompletion:(void (^)(void))completion
|
||||
{
|
||||
[[TGBridgeAudioDecoder processingQueue] dispatch:^
|
||||
{
|
||||
_opusReader = [[OggOpusReader alloc] initWithPath:_url.path];
|
||||
if (_opusReader == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription sourceFormat;
|
||||
sourceFormat.mSampleRate = TGBridgeAudioDecoderInputSampleRate;
|
||||
sourceFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
sourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
|
||||
sourceFormat.mFramesPerPacket = 1;
|
||||
sourceFormat.mChannelsPerFrame = 1;
|
||||
sourceFormat.mBitsPerChannel = 16;
|
||||
sourceFormat.mBytesPerPacket = 2;
|
||||
sourceFormat.mBytesPerFrame = 2;
|
||||
|
||||
AudioStreamBasicDescription destFormat;
|
||||
memset(&destFormat, 0, sizeof(destFormat));
|
||||
destFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame;
|
||||
destFormat.mFormatID = kAudioFormatMPEG4AAC;
|
||||
destFormat.mSampleRate = TGBridgeAudioDecoderResultSampleRate;
|
||||
UInt32 size = sizeof(destFormat);
|
||||
if (!checkResult(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &destFormat),
|
||||
"AudioFormatGetProperty(kAudioFormatProperty_FormatInfo)"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExtAudioFileRef destinationFile;
|
||||
if (!checkResult(ExtAudioFileCreateWithURL((__bridge CFURLRef)_resultURL, kAudioFileM4AType, &destFormat, NULL, kAudioFileFlags_EraseFile, &destinationFile), "ExtAudioFileCreateWithURL"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkResult(ExtAudioFileSetProperty(destinationFile, kExtAudioFileProperty_ClientDataFormat, size, &sourceFormat),
|
||||
"ExtAudioFileSetProperty(destinationFile, kExtAudioFileProperty_ClientDataFormat"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool canResumeAfterInterruption = false;
|
||||
AudioConverterRef converter;
|
||||
size = sizeof(converter);
|
||||
if (checkResult(ExtAudioFileGetProperty(destinationFile, kExtAudioFileProperty_AudioConverter, &size, &converter),
|
||||
"ExtAudioFileGetProperty(kExtAudioFileProperty_AudioConverter;)"))
|
||||
{
|
||||
UInt32 canResume = 0;
|
||||
size = sizeof(canResume);
|
||||
if (AudioConverterGetProperty(converter, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume) == noErr)
|
||||
canResumeAfterInterruption = canResume;
|
||||
}
|
||||
|
||||
uint8_t srcBuffer[TGBridgeAudioDecoderBufferSize];
|
||||
while (!_cancelled)
|
||||
{
|
||||
AudioBufferList bufferList;
|
||||
bufferList.mNumberBuffers = 1;
|
||||
bufferList.mBuffers[0].mNumberChannels = sourceFormat.mChannelsPerFrame;
|
||||
bufferList.mBuffers[0].mDataByteSize = TGBridgeAudioDecoderBufferSize;
|
||||
bufferList.mBuffers[0].mData = srcBuffer;
|
||||
|
||||
uint32_t writtenOutputBytes = 0;
|
||||
while (writtenOutputBytes < TGBridgeAudioDecoderBufferSize)
|
||||
{
|
||||
int32_t readSamples = [_opusReader read:(uint16_t *)(srcBuffer + writtenOutputBytes) bufSize:(TGBridgeAudioDecoderBufferSize - writtenOutputBytes) / sourceFormat.mBytesPerFrame];
|
||||
|
||||
if (readSamples > 0)
|
||||
writtenOutputBytes += readSamples * sourceFormat.mBytesPerFrame;
|
||||
else
|
||||
break;
|
||||
}
|
||||
bufferList.mBuffers[0].mDataByteSize = writtenOutputBytes;
|
||||
int32_t nFrames = writtenOutputBytes / sourceFormat.mBytesPerFrame;
|
||||
|
||||
if (nFrames == 0)
|
||||
break;
|
||||
|
||||
OSStatus status = ExtAudioFileWrite(destinationFile, nFrames, &bufferList);
|
||||
if (status == kExtAudioFileError_CodecUnavailableInputConsumed)
|
||||
{
|
||||
//TGLog(@"1");
|
||||
}
|
||||
else if (status == kExtAudioFileError_CodecUnavailableInputNotConsumed)
|
||||
{
|
||||
//TGLog(@"2");
|
||||
}
|
||||
else if (!checkResult(status, "ExtAudioFileWrite"))
|
||||
{
|
||||
//TGLog(@"3");
|
||||
}
|
||||
}
|
||||
|
||||
ExtAudioFileDispose(destinationFile);
|
||||
|
||||
if (completion != nil)
|
||||
completion();
|
||||
}];
|
||||
}
|
||||
|
||||
+ (SQueue *)processingQueue
|
||||
{
|
||||
static SQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
static const char *queueSpecific = "org.telegram.opusAudioDecoderQueue";
|
||||
dispatch_queue_t dispatchQueue = dispatch_queue_create("org.telegram.opusAudioDecoderQueue", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_queue_set_specific(dispatchQueue, queueSpecific, (void *)queueSpecific, NULL);
|
||||
queue = [SQueue wrapConcurrentNativeQueue:dispatchQueue];
|
||||
});
|
||||
return queue;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,564 @@
|
||||
#import <WatchBridgeAudioImpl/TGBridgeAudioEncoder.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import <OpusBinding/OpusBinding.h>
|
||||
|
||||
static const char *AMQueueSpecific = "AMQueueSpecific";
|
||||
|
||||
const NSInteger TGBridgeAudioEncoderSampleRate = 48000;
|
||||
|
||||
typedef enum {
|
||||
ATQueuePriorityLow,
|
||||
ATQueuePriorityDefault,
|
||||
ATQueuePriorityHigh
|
||||
} ATQueuePriority;
|
||||
|
||||
@interface ATQueue : NSObject
|
||||
|
||||
+ (ATQueue *)mainQueue;
|
||||
+ (ATQueue *)concurrentDefaultQueue;
|
||||
+ (ATQueue *)concurrentBackgroundQueue;
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithName:(NSString *)name;
|
||||
- (instancetype)initWithPriority:(ATQueuePriority)priority;
|
||||
|
||||
- (void)dispatch:(dispatch_block_t)block;
|
||||
- (void)dispatch:(dispatch_block_t)block synchronous:(bool)synchronous;
|
||||
- (void)dispatchAfter:(NSTimeInterval)seconds block:(dispatch_block_t)block;
|
||||
|
||||
- (dispatch_queue_t)nativeQueue;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGFileDataItem : TGDataItem
|
||||
|
||||
- (instancetype)initWithTempFile;
|
||||
|
||||
- (void)appendData:(NSData *)data;
|
||||
- (NSData *)readDataAtOffset:(NSUInteger)offset length:(NSUInteger)length;
|
||||
- (NSUInteger)length;
|
||||
|
||||
- (NSString *)path;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGBridgeAudioEncoder ()
|
||||
{
|
||||
AVAssetReader *_assetReader;
|
||||
AVAssetReaderOutput *_readerOutput;
|
||||
|
||||
NSMutableData *_audioBuffer;
|
||||
TGFileDataItem *_tempFileItem;
|
||||
TGOggOpusWriter *_oggWriter;
|
||||
|
||||
int _tailLength;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGBridgeAudioEncoder
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
|
||||
if (asset == nil || asset.tracks.count == 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
_assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
|
||||
|
||||
NSDictionary *outputSettings = @
|
||||
{
|
||||
AVFormatIDKey: @(kAudioFormatLinearPCM),
|
||||
AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate),
|
||||
AVNumberOfChannelsKey: @1,
|
||||
AVLinearPCMBitDepthKey: @16,
|
||||
AVLinearPCMIsFloatKey: @false,
|
||||
AVLinearPCMIsBigEndianKey: @false,
|
||||
AVLinearPCMIsNonInterleaved: @false
|
||||
};
|
||||
|
||||
_readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
|
||||
|
||||
[_assetReader addOutput:_readerOutput];
|
||||
|
||||
_tempFileItem = [[TGFileDataItem alloc] initWithTempFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self cleanup];
|
||||
}
|
||||
|
||||
- (void)cleanup
|
||||
{
|
||||
_oggWriter = nil;
|
||||
}
|
||||
|
||||
+ (ATQueue *)processingQueue
|
||||
{
|
||||
static ATQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioEncoderQueue"];
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
static const int encoderPacketSizeInBytes = 16000 / 1000 * 60 * 2;
|
||||
|
||||
- (void)startWithCompletion:(void (^)(NSString *, int32_t))completion
|
||||
{
|
||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||
{
|
||||
_oggWriter = [[TGOggOpusWriter alloc] init];
|
||||
if (![_oggWriter beginWithDataItem:_tempFileItem])
|
||||
{
|
||||
[self cleanup];
|
||||
return;
|
||||
}
|
||||
|
||||
[_assetReader startReading];
|
||||
|
||||
while (_assetReader.status != AVAssetReaderStatusCompleted)
|
||||
{
|
||||
if (_assetReader.status == AVAssetReaderStatusReading)
|
||||
{
|
||||
CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer];
|
||||
if (nextBuffer)
|
||||
{
|
||||
AudioBufferList abl;
|
||||
CMBlockBufferRef blockBuffer;
|
||||
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
|
||||
|
||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||
{
|
||||
[self _processBuffer:&abl.mBuffers[0]];
|
||||
|
||||
CFRelease(nextBuffer);
|
||||
CFRelease(blockBuffer);
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||
{
|
||||
if (_tailLength > 0) {
|
||||
[_oggWriter writeFrame:(uint8_t *)_audioBuffer.bytes frameByteCount:(NSUInteger)_tailLength];
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||
{
|
||||
TGFileDataItem *dataItemResult = nil;
|
||||
NSTimeInterval durationResult = 0.0;
|
||||
|
||||
NSUInteger totalBytes = 0;
|
||||
|
||||
if (_assetReader.status == AVAssetReaderStatusCompleted)
|
||||
{
|
||||
NSLog(@"finished");
|
||||
if (_oggWriter != nil && [_oggWriter writeFrame:NULL frameByteCount:0])
|
||||
{
|
||||
dataItemResult = _tempFileItem;
|
||||
durationResult = [_oggWriter encodedDuration];
|
||||
totalBytes = [_oggWriter encodedBytes];
|
||||
}
|
||||
|
||||
[self cleanup];
|
||||
}
|
||||
|
||||
//TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0);
|
||||
|
||||
if (completion != nil)
|
||||
completion(dataItemResult.path, (int32_t)durationResult);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_processBuffer:(AudioBuffer const *)buffer
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
if (_oggWriter == nil)
|
||||
return;
|
||||
|
||||
unsigned char currentEncoderPacket[encoderPacketSizeInBytes];
|
||||
|
||||
int bufferOffset = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int currentEncoderPacketSize = 0;
|
||||
|
||||
while (currentEncoderPacketSize < encoderPacketSizeInBytes)
|
||||
{
|
||||
if (_audioBuffer.length != 0)
|
||||
{
|
||||
int takenBytes = MIN((int)_audioBuffer.length, encoderPacketSizeInBytes - currentEncoderPacketSize);
|
||||
if (takenBytes != 0)
|
||||
{
|
||||
memcpy(currentEncoderPacket + currentEncoderPacketSize, _audioBuffer.bytes, takenBytes);
|
||||
[_audioBuffer replaceBytesInRange:NSMakeRange(0, takenBytes) withBytes:NULL length:0];
|
||||
currentEncoderPacketSize += takenBytes;
|
||||
}
|
||||
}
|
||||
else if (bufferOffset < (int)buffer->mDataByteSize)
|
||||
{
|
||||
int takenBytes = MIN((int)buffer->mDataByteSize - bufferOffset, encoderPacketSizeInBytes - currentEncoderPacketSize);
|
||||
if (takenBytes != 0)
|
||||
{
|
||||
memcpy(currentEncoderPacket + currentEncoderPacketSize, ((const char *)buffer->mData) + bufferOffset, takenBytes);
|
||||
bufferOffset += takenBytes;
|
||||
currentEncoderPacketSize += takenBytes;
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_tailLength = currentEncoderPacketSize;
|
||||
if (currentEncoderPacketSize < encoderPacketSizeInBytes)
|
||||
{
|
||||
if (_audioBuffer == nil)
|
||||
_audioBuffer = [[NSMutableData alloc] initWithCapacity:encoderPacketSizeInBytes];
|
||||
[_audioBuffer appendBytes:currentEncoderPacket length:currentEncoderPacketSize];
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[_oggWriter writeFrame:currentEncoderPacket frameByteCount:(NSUInteger)currentEncoderPacketSize];
|
||||
_tailLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TGFileDataItem ()
|
||||
{
|
||||
NSUInteger _length;
|
||||
|
||||
NSString *_fileName;
|
||||
bool _fileExists;
|
||||
|
||||
NSMutableData *_data;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TGFileDataItem
|
||||
{
|
||||
ATQueue *_queue;
|
||||
}
|
||||
|
||||
- (void)_commonInit
|
||||
{
|
||||
_queue = [[ATQueue alloc] initWithPriority:ATQueuePriorityLow];
|
||||
_data = [[NSMutableData alloc] init];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTempFile
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
[self _commonInit];
|
||||
|
||||
[_queue dispatch:^
|
||||
{
|
||||
int64_t randomId = 0;
|
||||
arc4random_buf(&randomId, 8);
|
||||
_fileName = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%" PRIx64 "", randomId]];
|
||||
_fileExists = false;
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
[self _commonInit];
|
||||
|
||||
|
||||
[_queue dispatch:^
|
||||
{
|
||||
_fileName = filePath;
|
||||
_length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue];
|
||||
_fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)noop
|
||||
{
|
||||
}
|
||||
|
||||
- (void)moveToPath:(NSString *)path
|
||||
{
|
||||
[_queue dispatch:^
|
||||
{
|
||||
[[NSFileManager defaultManager] moveItemAtPath:_fileName toPath:path error:nil];
|
||||
_fileName = path;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)remove
|
||||
{
|
||||
[_queue dispatch:^
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_fileName error:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)appendData:(NSData *)data
|
||||
{
|
||||
[_queue dispatch:^
|
||||
{
|
||||
if (!_fileExists)
|
||||
{
|
||||
[[NSFileManager defaultManager] createFileAtPath:_fileName contents:nil attributes:nil];
|
||||
_fileExists = true;
|
||||
}
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
||||
[file seekToEndOfFile];
|
||||
[file writeData:data];
|
||||
[file synchronizeFile];
|
||||
[file closeFile];
|
||||
_length += data.length;
|
||||
|
||||
[_data appendData:data];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSData *)readDataAtOffset:(NSUInteger)offset length:(NSUInteger)length
|
||||
{
|
||||
__block NSData *data = nil;
|
||||
|
||||
[_queue dispatch:^
|
||||
{
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
||||
[file seekToFileOffset:(unsigned long long)offset];
|
||||
data = [file readDataOfLength:length];
|
||||
if (data.length != length)
|
||||
//TGLog(@"Read data length mismatch");
|
||||
[file closeFile];
|
||||
} synchronous:true];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
__block NSUInteger result = 0;
|
||||
[_queue dispatch:^
|
||||
{
|
||||
result = _length;
|
||||
} synchronous:true];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)path {
|
||||
return _fileName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ATQueue ()
|
||||
{
|
||||
dispatch_queue_t _nativeQueue;
|
||||
bool _isMainQueue;
|
||||
|
||||
int32_t _noop;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ATQueue
|
||||
|
||||
+ (NSString *)applicationPrefix
|
||||
{
|
||||
static NSString *prefix = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
prefix = [[NSBundle mainBundle] bundleIdentifier];
|
||||
});
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
+ (ATQueue *)mainQueue
|
||||
{
|
||||
static ATQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
queue = [[ATQueue alloc] init];
|
||||
queue->_nativeQueue = dispatch_get_main_queue();
|
||||
queue->_isMainQueue = true;
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
+ (ATQueue *)concurrentDefaultQueue
|
||||
{
|
||||
static ATQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
+ (ATQueue *)concurrentBackgroundQueue
|
||||
{
|
||||
static ATQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)];
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithName:[[ATQueue applicationPrefix] stringByAppendingFormat:@".%ld", lrand48()]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_nativeQueue = dispatch_queue_create([name UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_queue_set_specific(_nativeQueue, AMQueueSpecific, (__bridge void *)self, NULL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPriority:(ATQueuePriority)priority
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_nativeQueue = dispatch_queue_create([[[ATQueue applicationPrefix] stringByAppendingFormat:@".%ld", lrand48()] UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
long targetQueueIdentifier = DISPATCH_QUEUE_PRIORITY_DEFAULT;
|
||||
switch (priority)
|
||||
{
|
||||
case ATQueuePriorityLow:
|
||||
targetQueueIdentifier = DISPATCH_QUEUE_PRIORITY_LOW;
|
||||
break;
|
||||
case ATQueuePriorityDefault:
|
||||
targetQueueIdentifier = DISPATCH_QUEUE_PRIORITY_DEFAULT;
|
||||
break;
|
||||
case ATQueuePriorityHigh:
|
||||
targetQueueIdentifier = DISPATCH_QUEUE_PRIORITY_HIGH;
|
||||
break;
|
||||
}
|
||||
dispatch_set_target_queue(_nativeQueue, dispatch_get_global_queue(targetQueueIdentifier, 0));
|
||||
dispatch_queue_set_specific(_nativeQueue, AMQueueSpecific, (__bridge void *)self, NULL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNativeQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
_nativeQueue = dispatch_retain(queue);
|
||||
#else
|
||||
_nativeQueue = queue;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_nativeQueue != nil)
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_nativeQueue);
|
||||
#endif
|
||||
_nativeQueue = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dispatch:(dispatch_block_t)block
|
||||
{
|
||||
[self dispatch:block synchronous:false];
|
||||
}
|
||||
|
||||
- (void)dispatch:(dispatch_block_t)block synchronous:(bool)synchronous
|
||||
{
|
||||
__block ATQueue *strongSelf = self;
|
||||
dispatch_block_t blockWithSelf = ^
|
||||
{
|
||||
block();
|
||||
[strongSelf noop];
|
||||
strongSelf = nil;
|
||||
};
|
||||
|
||||
if (_isMainQueue)
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
blockWithSelf();
|
||||
else if (synchronous)
|
||||
dispatch_sync(_nativeQueue, blockWithSelf);
|
||||
else
|
||||
dispatch_async(_nativeQueue, blockWithSelf);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dispatch_get_specific(AMQueueSpecific) == (__bridge void *)self)
|
||||
block();
|
||||
else if (synchronous)
|
||||
dispatch_sync(_nativeQueue, blockWithSelf);
|
||||
else
|
||||
dispatch_async(_nativeQueue, blockWithSelf);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dispatchAfter:(NSTimeInterval)seconds block:(dispatch_block_t)block
|
||||
{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC)), _nativeQueue, block);
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)nativeQueue
|
||||
{
|
||||
return _nativeQueue;
|
||||
}
|
||||
|
||||
- (void)noop
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import WatchBridgeAudioImpl
|
||||
|
||||
public func legacyDecodeOpusAudio(path: String, outputPath: String) -> Signal<String, NoError> {
|
||||
return Signal { subscriber in
|
||||
let decoder = TGBridgeAudioDecoder(url: URL(fileURLWithPath: path), outputUrl: URL(fileURLWithPath: outputPath))
|
||||
decoder?.start(completion: {
|
||||
subscriber.putNext(outputPath)
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func legacyEncodeOpusAudio(path: String) -> Signal<(String?, Int32), NoError> {
|
||||
return Signal { subscriber in
|
||||
let encoder = TGBridgeAudioEncoder(url: URL(fileURLWithPath: path))
|
||||
encoder?.start(completion: { (path, duration) in
|
||||
subscriber.putNext((path, duration))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user