I'm working on a unit test for one of my model which is using asynchronized call to my rest api.The method used to request my API is like this:
requestOnComplete:(void(^)())complete onError:(void(^)(NSString* errMsg))fail;
In my test case:
-(void)testMyApiCall
{
[myObj requestOnComplete:^{
XCTAssertTrue(YES,#"Success");
} onError:^(NSString *errorString) {
XCTFail(#"Failed.%#", errorString);
}];
}
As I expected, this test always pass because of the asynchronized call. Can anybody advise on this issue? Thanks.
You can use lib XCAsyncTestCase
It simple to do XCTestCas asynchronized method.
Ex as your test function here is your code:
-(void)testMyApiCall
{
[myObj requestOnComplete:^{
[self notify:XCTestAsyncTestCaseStatusSucceeded];
} onError:^(NSString *errorString) {
[self notify:XCTestAsyncTestCaseStatusFailed];
}];
[self waitForStatus:XCTestAsyncTestCaseStatusSucceeded timeout:10];
}
I use these helper functions
BOOL XLCRunloopRunUntil(CFTimeInterval timeout, BOOL (^condition)(void));
#define XLCAssertTrueBeforeTimeout(expr, timeout, format...) \
XCTAssertTrue( (XLCRunloopRunUntil(timeout, ^BOOL{ return expr; })) , ## format )
static inline void XLCRunloopRunOnce()
{
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, YES) == kCFRunLoopRunHandledSource ||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES) == kCFRunLoopRunHandledSource);
}
static inline void XLCRunloopRun(CFTimeInterval timeout)
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, NO);
XLCRunloopRunOnce();
}
BOOL XLCRunloopRunUntil(CFTimeInterval timeout, BOOL (^condition)(void)) {
static mach_timebase_info_data_t timebaseInfo;
if ( timebaseInfo.denom == 0 ) {
mach_timebase_info(&timebaseInfo);
}
uint64_t timeoutNano = timeout * 1e9;
uint64_t start = mach_absolute_time();
do {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, YES);
XLCRunloopRunOnce();
uint64_t end = mach_absolute_time();
uint64_t elapsed = end - start;
uint64_t elapseNano = elapsed * timebaseInfo.numer / timebaseInfo.denom;
if (elapseNano >= timeoutNano) {
return NO;
}
} while (!condition());
return YES;
}
example
-(void)testMyApiCall
{
__block BOOL done = NO;
[myObj requestOnComplete:^{
// XCTAssertTrue(YES,#"Success"); // this line is pointless
done = YES;
} onError:^(NSString *errorString) {
XCTFail(#"Failed.%#", errorString);
done = YES;
}];
XLCAssertTrueBeforeTimeout(done, 1, "should finish within 1 seconds");
}
Related
I am looking to get latitude and longitude values from an IOS plugin.The plugin code is given below which is a .mm file.
#import "iPhone_Sensors.h"
#import <CoreLocation/CoreLocation.h>
#import <CoreMotion/CoreMotion.h>
#include "iPhone_View.h"
#include "iPhone_OrientationSupport.h"
static bool gCompensateSensors = true;
bool gEnableGyroscope = false;
bool IsCompensatingSensors() { return gCompensateSensors; }
void SetCompensatingSensors(bool val) { gCompensateSensors = val;}
void UnityDidAccelerate(float x, float y, float z, NSTimeInterval timestamp);
struct Vector3f
{
float x, y, z;
};
struct Quaternion4f
{
float x, y, z, w;
};
inline float UnityReorientHeading(float heading)
{
if (IsCompensatingSensors())
{
float rotateBy = 0.f;
switch (UnityCurrentOrientation())
{
case portraitUpsideDown:
rotateBy = -180.f;
break;
case landscapeLeft:
rotateBy = -270.f;
break;
case landscapeRight:
rotateBy = -90.f;
break;
default:
break;
}
return fmodf((360.f + heading + rotateBy), 360.f);
}
else
{
return heading;
}
}
inline Vector3f UnityReorientVector3(float x, float y, float z)
{
if (IsCompensatingSensors())
{
Vector3f res;
switch (UnityCurrentOrientation())
{
case portraitUpsideDown:
{ res = (Vector3f){-x, -y, z}; }
break;
case landscapeLeft:
{ res = (Vector3f){-y, x, z}; }
break;
case landscapeRight:
{ res = (Vector3f){y, -x, z}; }
break;
default:
{ res = (Vector3f){x, y, z}; }
}
return res;
}
else
{
return (Vector3f){x, y, z};
}
}
static Quaternion4f gQuatRot[4] =
{ // { x*sin(theta/2), y*sin(theta/2), z*sin(theta/2), cos(theta/2) }
// => { 0, 0, sin(theta/2), cos(theta/2) } (since <vec> = { 0, 0, +/-1})
{ 0.f, 0.f, 0.f /*sin(0)*/, 1.f /*cos(0)*/}, // ROTATION_0, theta = 0 rad
{ 0.f, 0.f, (float)sqrt(2) * 0.5f /*sin(pi/4)*/, -(float)sqrt(2) * 0.5f /*cos(pi/4)*/}, // ROTATION_90, theta = pi/4 rad
{ 0.f, 0.f, 1.f /*sin(pi/2)*/, 0.f /*cos(pi/2)*/}, // ROTATION_180, theta = pi rad
{ 0.f, 0.f, -(float)sqrt(2) * 0.5f/*sin(3pi/4)*/, -(float)sqrt(2) * 0.5f /*cos(3pi/4)*/} // ROTATION_270, theta = 3pi/2 rad
};
inline void MultQuat(Quaternion4f& result, const Quaternion4f& lhs, const Quaternion4f& rhs)
{
result.x = lhs.w*rhs.x + lhs.x*rhs.w + lhs.y*rhs.z - lhs.z*rhs.y;
result.y = lhs.w*rhs.y + lhs.y*rhs.w + lhs.z*rhs.x - lhs.x*rhs.z;
result.z = lhs.w*rhs.z + lhs.z*rhs.w + lhs.x*rhs.y - lhs.y*rhs.x;
result.w = lhs.w*rhs.w - lhs.x*rhs.x - lhs.y*rhs.y - lhs.z*rhs.z;
}
inline Quaternion4f UnityReorientQuaternion(float x, float y, float z, float w)
{
if (IsCompensatingSensors())
{
Quaternion4f res, inp = {x, y, z, w};
switch (UnityCurrentOrientation())
{
case landscapeLeft:
MultQuat(res, inp, gQuatRot[1]);
break;
case portraitUpsideDown:
MultQuat(res, inp, gQuatRot[2]);
break;
case landscapeRight:
MultQuat(res, inp, gQuatRot[3]);
break;
default:
res = inp;
}
return res;
}
else
{
return (Quaternion4f){x, y, z, w};
}
}
void SetGyroRotationRate(int idx, float x, float y, float z);
void SetGyroRotationRateUnbiased(int idx, float x, float y, float z);
void SetGravity(int idx, float x, float y, float z);
void SetUserAcceleration(int idx, float x, float y, float z);
void SetAttitude(int idx, float x, float y, float z, float w);
static CMMotionManager *sMotionManager = nil;
static NSOperationQueue* sMotionQueue = nil;
// Current update interval or 0.0f if not initialized. This is returned
// to the user as current update interval and this value is set to 0.0f when
// gyroscope is disabled.
static float sUpdateInterval = 0.0f;
// Update interval set by the user. Core motion will be set-up to use
// this update interval after disabling and re-enabling gyroscope
// so users can set update interval, disable gyroscope, enable gyroscope and
// after that gyroscope will be updated at this previously set interval.
static float sUserUpdateInterval = 1.0f / 30.0f;
void SensorsCleanup()
{
if (sMotionManager != nil)
{
[sMotionManager stopGyroUpdates];
[sMotionManager stopDeviceMotionUpdates];
[sMotionManager stopAccelerometerUpdates];
[sMotionManager release];
sMotionManager = nil;
}
if (sMotionQueue != nil)
{
[sMotionQueue release];
sMotionQueue = nil;
}
}
void CoreMotionStart()
{
if (sMotionQueue == nil)
sMotionQueue = [[NSOperationQueue alloc] init];
if (sMotionManager == nil)
{
sMotionManager = [[CMMotionManager alloc] init];
if (sMotionManager.gyroAvailable && gEnableGyroscope)
{
[sMotionManager startGyroUpdates];
[sMotionManager setGyroUpdateInterval: sUpdateInterval];
}
if (sMotionManager.deviceMotionAvailable && gEnableGyroscope)
{
[sMotionManager startDeviceMotionUpdates];
[sMotionManager setDeviceMotionUpdateInterval: sUpdateInterval];
}
if (sMotionManager.accelerometerAvailable)
{
int frequency = UnityGetAccelerometerFrequency();
if (frequency > 0)
{
[sMotionManager startAccelerometerUpdatesToQueue: sMotionQueue withHandler: ^( CMAccelerometerData* data, NSError* error) {
Vector3f res = UnityReorientVector3(data.acceleration.x, data.acceleration.y, data.acceleration.z);
UnityDidAccelerate(res.x, res.y, res.z, data.timestamp);
}];
[sMotionManager setAccelerometerUpdateInterval: 1.0 / frequency];
}
}
}
}
void CoreMotionStop()
{
if (sMotionManager != nil)
{
[sMotionManager stopGyroUpdates];
[sMotionManager stopDeviceMotionUpdates];
}
}
void SetGyroUpdateInterval(int idx, float interval)
{
if (interval < (1.0f / 60.0f))
interval = (1.0f / 60.0f);
else if (interval > (1.0f))
interval = 1.0f;
sUserUpdateInterval = interval;
if (sMotionManager)
{
sUpdateInterval = interval;
[sMotionManager setGyroUpdateInterval: interval];
[sMotionManager setDeviceMotionUpdateInterval: interval];
}
}
float GetGyroUpdateInterval(int idx)
{
return sUpdateInterval;
}
void UpdateGyroData()
{
CMRotationRate rotationRate = { 0.0, 0.0, 0.0 };
CMRotationRate rotationRateUnbiased = { 0.0, 0.0, 0.0 };
CMAcceleration userAcceleration = { 0.0, 0.0, 0.0 };
CMAcceleration gravity = { 0.0, 0.0, 0.0 };
CMQuaternion attitude = { 0.0, 0.0, 0.0, 1.0 };
if (sMotionManager != nil)
{
CMGyroData *gyroData = sMotionManager.gyroData;
CMDeviceMotion *motionData = sMotionManager.deviceMotion;
if (gyroData != nil)
{
rotationRate = gyroData.rotationRate;
}
if (motionData != nil)
{
CMAttitude *att = motionData.attitude;
attitude = att.quaternion;
rotationRateUnbiased = motionData.rotationRate;
userAcceleration = motionData.userAcceleration;
gravity = motionData.gravity;
}
}
Vector3f reorientedRotRate = UnityReorientVector3(rotationRate.x, rotationRate.y, rotationRate.z);
SetGyroRotationRate(0, reorientedRotRate.x, reorientedRotRate.y, reorientedRotRate.z);
Vector3f reorientedRotRateUnbiased = UnityReorientVector3(rotationRateUnbiased.x, rotationRateUnbiased.y, rotationRateUnbiased.z);
SetGyroRotationRateUnbiased(0, reorientedRotRateUnbiased.x, reorientedRotRateUnbiased.y, reorientedRotRateUnbiased.z);
Vector3f reorientedUserAcc = UnityReorientVector3(userAcceleration.x, userAcceleration.y, userAcceleration.z);
SetUserAcceleration(0, reorientedUserAcc.x, reorientedUserAcc.y, reorientedUserAcc.z);
Vector3f reorientedG = UnityReorientVector3(gravity.x, gravity.y, gravity.z);
SetGravity(0, reorientedG.x, reorientedG.y, reorientedG.z);
Quaternion4f reorientedAtt = UnityReorientQuaternion(attitude.x, attitude.y, attitude.z, attitude.w);
SetAttitude(0, reorientedAtt.x, reorientedAtt.y, reorientedAtt.z, reorientedAtt.w);
}
bool IsGyroEnabled(int idx)
{
if (sMotionManager == nil)
return false;
return sMotionManager.gyroAvailable && sMotionManager.gyroActive;
}
bool IsGyroAvailable()
{
if (sMotionManager != nil)
return sMotionManager.gyroAvailable;
return false;
}
#interface LocationServiceDelegate : NSObject <CLLocationManagerDelegate>
#end
void
UnitySetLastLocation(double timestamp,
float latitude,
float longitude,
float altitude,
float horizontalAccuracy,
float verticalAccuracy);
void
UnitySetLastHeading(float magneticHeading,
float trueHeading,
float rawX, float rawY, float rawZ,
double timestamp);
struct LocationServiceInfo
{
private:
LocationServiceDelegate* delegate;
CLLocationManager* locationManager;
public:
LocationServiceStatus locationStatus;
LocationServiceStatus headingStatus;
float desiredAccuracy;
float distanceFilter;
LocationServiceInfo();
CLLocationManager* GetLocationManager();
};
LocationServiceInfo::LocationServiceInfo()
{
locationStatus = kLocationServiceStopped;
desiredAccuracy = kCLLocationAccuracyKilometer;
distanceFilter = 500;
headingStatus = kLocationServiceStopped;
}
static LocationServiceInfo gLocationServiceStatus;
CLLocationManager*
LocationServiceInfo::GetLocationManager()
{
if (locationManager == nil)
{
locationManager = [[CLLocationManager alloc] init];
delegate = [LocationServiceDelegate alloc];
locationManager.delegate = delegate;
}
return locationManager;
}
bool LocationService::IsServiceEnabledByUser()
{
return [CLLocationManager locationServicesEnabled];
}
void LocationService::SetDesiredAccuracy(float val)
{
gLocationServiceStatus.desiredAccuracy = val;
}
float LocationService::GetDesiredAccuracy()
{
return gLocationServiceStatus.desiredAccuracy;
}
void LocationService::SetDistanceFilter(float val)
{
gLocationServiceStatus.distanceFilter = val;
}
float LocationService::GetDistanceFilter()
{
return gLocationServiceStatus.distanceFilter;
}
void LocationService::StartUpdatingLocation()
{
if (gLocationServiceStatus.locationStatus != kLocationServiceRunning)
{
CLLocationManager* locationManager = gLocationServiceStatus.GetLocationManager();
locationManager.desiredAccuracy = gLocationServiceStatus.desiredAccuracy;
// Set a movement threshold for new events
locationManager.distanceFilter = gLocationServiceStatus.distanceFilter;
[locationManager startUpdatingLocation];
gLocationServiceStatus.locationStatus = kLocationServiceInitializing;
}
}
void LocationService::StopUpdatingLocation()
{
if (gLocationServiceStatus.locationStatus == kLocationServiceRunning)
{
[gLocationServiceStatus.GetLocationManager() stopUpdatingLocation];
gLocationServiceStatus.locationStatus = kLocationServiceStopped;
}
}
void LocationService::SetHeadingUpdatesEnabled(bool enabled)
{
if (enabled)
{
if (gLocationServiceStatus.headingStatus != kLocationServiceRunning &&
IsHeadingAvailable())
{
CLLocationManager* locationManager = gLocationServiceStatus.GetLocationManager();
[locationManager startUpdatingHeading];
gLocationServiceStatus.headingStatus = kLocationServiceInitializing;
}
}
else
{
if(gLocationServiceStatus.headingStatus == kLocationServiceRunning)
{
[gLocationServiceStatus.GetLocationManager() stopUpdatingHeading];
gLocationServiceStatus.headingStatus = kLocationServiceStopped;
}
}
}
bool LocationService::IsHeadingUpdatesEnabled()
{
return (gLocationServiceStatus.headingStatus == kLocationServiceRunning);
}
int UnityGetLocationStatus()
{
return gLocationServiceStatus.locationStatus;
}
int UnityGetHeadingStatus()
{
return gLocationServiceStatus.headingStatus;
}
bool LocationService::IsHeadingAvailable()
{
return [CLLocationManager headingAvailable];
}
#implementation LocationServiceDelegate
// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
gLocationServiceStatus.locationStatus = kLocationServiceRunning;
UnitySetLastLocation([newLocation.timestamp timeIntervalSince1970],
newLocation.coordinate.latitude,
newLocation.coordinate.longitude,
newLocation.altitude,
newLocation.horizontalAccuracy,
newLocation.verticalAccuracy);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
gLocationServiceStatus.headingStatus = kLocationServiceRunning;
Vector3f reorientedRawHeading = UnityReorientVector3(newHeading.x, newHeading.y, newHeading.z);
UnitySetLastHeading(UnityReorientHeading(newHeading.magneticHeading),
UnityReorientHeading(newHeading.trueHeading),
reorientedRawHeading.x, reorientedRawHeading.y, reorientedRawHeading.z,
[newHeading.timestamp timeIntervalSince1970]);
}
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
return NO;
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error;
{
gLocationServiceStatus.locationStatus = kLocationServiceFailed;
gLocationServiceStatus.headingStatus = kLocationServiceFailed;
}
#end
I followed this link to extract the required values from the plugin but cant understand how it works.Plugin into unity
The code below is how I tried to get the latitudes and longitudes from plugin.I wrote a script and added a method
public class Getposition : MonoBehaviour {
#if UNITY_IOS
[DllImport("__Internal")]
********* // Here Which Method should I call?? I am calling from the plugin??
//I have to pass any values?? //*********
private static extern float UnitySetLastHeading(double timestamp,
float latitude,
float longitude,
float altitude,
float horizontalAccuracy,
float verticalAccuracy);
#endif
}
public getlatlonginfo( /*what all values required */)
{
//Get values from Plugin??
//string latitudevalues=?
//string longitudevalues=?
}
What I am trying to achieve is given below
Get the values that are picked up in iPhone_Sensors.mm, convert them into a string, and pass them into Unity
I am trying convert them to string so that I can get more decimal point and thus increase the accuracy.Not that expert with plugins.
There are two ways that I can think of.. First is to use a struct and return the coordinates directly.. The second is to register a callback/handler for when location does update.. You should really take a look at: https://www.mono-project.com/docs/advanced/pinvoke/
Anyway.. some code:
C++ side:
//Convert a C++ string to NSString
NSString* CPPStringToIOSString(const char* cppString)
{
return cppString ? [NSString stringWithUTF8String:cppString] : nullptr;
}
//Convert an NSString to C++ string.
const char* IOSStringToCPPString(NSString *iosString)
{
if ([iosString length])
{
const char* str = [iosString UTF8String];
if (str && strlen(str))
{
char* unityString = static_cast<char*>(malloc(strlen(str) + 1));
strcpy(unityString, str);
return unityString;
}
}
return nullptr;
}
static LocationServiceInfo GetLocationServiceInfo()
{
static LocationServiceInfo info;
return info;
}
//Exports:
extern "C" {
//When called, returns a CLLocationCoordinate2D to Unity.
CLLocationCoordinate2D _GetCoordinates()
{
CLLocationManager *manager = GetLocationServiceInfo().GetLocationManager();
return [manager getCurrentLocation];
}
//When called, registers a listen with LocationManager.
//The listener is called whenever the location has updated/changed.
void _RegisterLocationUpdateHandler(void(*callback)(double, double))
{
//Implement your own logic for this..
//Then call `callback` with the lat and lon.
GetLocationServiceInfo().onLocationDidUpdate = ^(double lat, double lon) {
if (callback) {
callback(lat, lon);
}
};
}
}
C# side (Plugin)..
//Struct representing coordinates received from the iOS plugin.
public struct CLLocationCoordinate2D {
double latitude;
double longitude;
}
//Imported function.
[DllImport ("__Internal")]
private static extern CLLocationCoordinate2D _GetCoordinates();
//Call the imported `GetCoordinates` when this function is called.
public static CLLocationCoordinate2D GetCoordinates()
{
return _GetCoordinates();
}
//------ Async ------
//Callback function pointer declaration..
public delegate void LocationCallbackFunc(double latitude, double longitude);
//Imported RegisterLocationUpdate function
[DllImport ("__Internal")]
private static extern void _RegisterLocationUpdateHandler(LocationCallback funcPtr);
//This function is called whenever location changed and has been updated via the iOS plugin..
private static void LocationDidChange(double latitude, double longitude)
{
Debug.Log(latitude);
Debug.Log(longitude);
}
//Call this function to register the location handler callback to be notified when location changes on the iOS side..
private static void RegisterLocationUpdateHandler()
{
_RegisterLocationUpdateHandler(new LocationCallbackFunc(this.LocationDidChange))
}
Recently, my project wants to achieving audio communication by AAC, so i use the AudioQueue, but there is a problem that my player's callback function doesn't work, in my project the callback function has worked 3 times. that is for function work 3 times. and when active AudioQueueStart(mQueue, NULL), the callback function never be called.
I use two iPhone to run. they are connect by udp socket, and i'm sure this part is fine. i can get the correct audio data.
And i modify a demo which is play a file data not memory. so i don't know is it a problem?
here is my code: AQPlayer.h
#include <AudioToolbox/AudioToolbox.h>
#include "CAStreamBasicDescription.h"
#include "CAXException.h"
#define kNumberBuffers 3
#define kBufferDurationSeconds 0.5
class AQPlayer
{
public:
AQPlayer();
~AQPlayer();
OSStatus StartQueue(BOOL inResume);
OSStatus StopQueue();
OSStatus PauseQueue();
AudioQueueRef Queue() { return mQueue; }
CAStreamBasicDescription DataFormat() const { return mDataFormat; }
Boolean IsRunning() const { return (mIsRunning) ? true : false; }
Boolean IsInitialized() const { return mIsInitialized; }
CFStringRef GetFilePath() const { return (mFilePath) ? mFilePath : CFSTR(""); }
Boolean IsLooping() const { return mIsLooping; }
void SetLooping(Boolean inIsLooping) { mIsLooping = inIsLooping; }
void CreateQueueForFile(CFStringRef inFilePath);
void DisposeQueue(Boolean inDisposeFile);
void prepareAudioQueue();
void start();
void stop();
private:
UInt32 GetNumPacketsToRead() { return mNumPacketsToRead; }
SInt64 GetCurrentPacket() { return mCurrentPacket; }
AudioFileID GetAudioFileID() { return mAudioFile; }
void SetCurrentPacket(SInt64 inPacket) { mCurrentPacket = inPacket; }
void SetupNewQueue();
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
AudioFileID mAudioFile;
CFStringRef mFilePath;
CAStreamBasicDescription mDataFormat;
Boolean mIsInitialized;
UInt32 mNumPacketsToRead;
SInt64 mCurrentPacket;
UInt32 mIsRunning;
Boolean mIsDone;
Boolean mIsLooping;
static void isRunningProc( void * inUserData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID);
static void AQBufferCallback( void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer);
void CalculateBytesForTime( CAStreamBasicDescription & inDesc,
UInt32 inMaxPacketSize,
Float64 inSeconds,
UInt32 *outBufferSize,
UInt32 *outNumPackets);
};
and here is AQPlayer.mm
#include "package.h"
#include "udpsocket.h"
#define MAXPACKETSIZE 1000
#define BUFFER_SIZE 4000
#include "AQPlayer.h"
extern udpsocket *udp;
void AQPlayer::AQBufferCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer)
{
AQPlayer *THIS = (AQPlayer *)inUserData;
dispatch_semaphore_wait(udp->sempahore,DISPATCH_TIME_FOREVER);
NSLog(#"read begin");
while ([udp->AudioQueue count]>0 &&
![[udp->AudioQueue objectAtIndex:0] isKindOfClass:[AUDIO_CACHE_OBJECT class]])
{
[udp->AudioQueue removeObjectAtIndex:0];
}
if([udp->AudioQueue count]<1){
[NSThread sleepForTimeInterval:0.05];
AQBufferCallback(inUserData, inAQ, inCompleteAQBuffer);
return;
}
int packets = 0;
int dataLen = 0;
AUDIO_CACHE_OBJECT *pack;
char *data = (char*)malloc(sizeof(char)*BUFFER_SIZE);;
memset(data, 0, BUFFER_SIZE);
pack = [udp->AudioQueue firstObject];
while (dataLen+pack.datalen<BUFFER_SIZE && [udp->AudioQueue count]>0 /*&& packets<21*/) {
memcpy(data+dataLen, [pack GetData], [pack datalen]);
dataLen+=[pack datalen];
[udp->AudioQueue removeObjectAtIndex:0];
// [pack memset];
packets ++;
while ([udp->AudioQueue count]>1 &&
![[udp->AudioQueue objectAtIndex:0] isKindOfClass:[AUDIO_CACHE_OBJECT class]])
{
[udp->AudioQueue removeObjectAtIndex:0];
}
if([udp->AudioQueue count]<1){
break;
}
pack = [udp->AudioQueue firstObject];
}
memcpy(inCompleteAQBuffer->mAudioData, data, dataLen);
inCompleteAQBuffer->mAudioDataByteSize = dataLen;
inCompleteAQBuffer->mPacketDescriptionCount = packets;
AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0,NULL);
THIS->mCurrentPacket += packets;
free(data);
NSLog(#"read end --- %lld",THIS->mCurrentPacket);
}
void AQPlayer::isRunningProc ( void * inUserData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
{
AQPlayer *THIS = (AQPlayer *)inUserData;
UInt32 size = sizeof(THIS->mIsRunning);
OSStatus result = AudioQueueGetProperty (inAQ, kAudioQueueProperty_IsRunning, &THIS->mIsRunning, &size);
if ((result == noErr) && (!THIS->mIsRunning))
[[NSNotificationCenter defaultCenter] postNotificationName: #"playbackQueueStopped" object: nil];
}
void AQPlayer::CalculateBytesForTime (CAStreamBasicDescription & inDesc, UInt32 inMaxPacketSize, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets)
{
// we only use time here as a guideline
// we're really trying to get somewhere between 16K and 64K buffers, but not allocate too much if we don't need it
static const int maxBufferSize = 0x10000; // limit size to 64K
static const int minBufferSize = 0x4000; // limit size to 16K
if (inDesc.mFramesPerPacket) {
Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds;
*outBufferSize = numPacketsForTime * inMaxPacketSize;
} else {
// if frames per packet is zero, then the codec has no predictable packet == time
// so we can't tailor this (we don't know how many Packets represent a time period
// we'll just return a default buffer size
*outBufferSize = maxBufferSize > inMaxPacketSize ? maxBufferSize : inMaxPacketSize;
}
// we're going to limit our size to our default
if (*outBufferSize > maxBufferSize && *outBufferSize > inMaxPacketSize)
*outBufferSize = maxBufferSize;
else {
// also make sure we're not too small - we don't want to go the disk for too small chunks
if (*outBufferSize < minBufferSize)
*outBufferSize = minBufferSize;
}
*outNumPackets = *outBufferSize / inMaxPacketSize;
}
AQPlayer::AQPlayer() :
mQueue(0),
mAudioFile(0),
mFilePath(NULL),
mIsRunning(false),
mIsInitialized(false),
mNumPacketsToRead(0),
mCurrentPacket(0),
mIsDone(false),
mIsLooping(false) { }
AQPlayer::~AQPlayer()
{
DisposeQueue(true);
}
OSStatus AQPlayer::StartQueue(BOOL inResume)
{
if (mQueue == NULL)
CreateQueueForFile(mFilePath);
mIsDone = false;
if (!inResume)
mCurrentPacket = 0;
for (int i = 0; i < kNumberBuffers; ++i) {
AQBufferCallback (this, mQueue, mBuffers[i]);
}
NSLog(#"audioqueuestart");
UInt32 i =0;
AudioQueuePrime(mQueue, 0, &i);
NSLog(#"%d",(unsigned int)i);
return AudioQueueStart(mQueue, NULL);
}
OSStatus AQPlayer::StopQueue()
{
OSStatus result = AudioQueueStop(mQueue, true);
if (result) printf("ERROR STOPPING QUEUE!\n");
return result;
}
OSStatus AQPlayer::PauseQueue()
{
OSStatus result = AudioQueuePause(mQueue);
return result;
}
void AQPlayer::CreateQueueForFile(CFStringRef inFilePath)
{
try {
UInt32 size = sizeof(mDataFormat);
mDataFormat.mSampleRate = 44100;
mDataFormat.mFormatID = kAudioFormatMPEG4AAC;
mDataFormat.mFormatFlags = 0;
mDataFormat.mFramesPerPacket = 1024;
mDataFormat.mChannelsPerFrame = 2;
mDataFormat.mBitsPerChannel = 0;//表示这是一个压缩格式
mDataFormat.mBytesPerPacket = 0;//表示这是一个变比特率压缩
mDataFormat.mBytesPerFrame = 0;
mDataFormat.mReserved = 0;
//aqc.bufferByteSize = 2000;
SetupNewQueue();
}
catch (NSException *e) {
fprintf(stderr, "Error: %# (%#)\n", [e debugDescription], [e description]);
}
}
void AQPlayer::SetupNewQueue()
{
AudioQueueNewOutput(&mDataFormat, AQPlayer::AQBufferCallback, this,//NULL,NULL,0,&mQueue);
CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &mQueue);
UInt32 bufferByteSize;
UInt32 maxPacketSize = MAXPACKETSIZE;
UInt32 size = sizeof(maxPacketSize);
CalculateBytesForTime (mDataFormat, maxPacketSize, kBufferDurationSeconds, &bufferByteSize, &mNumPacketsToRead);
size = sizeof(UInt32);
AudioQueueAddPropertyListener(mQueue, kAudioQueueProperty_IsRunning, isRunningProc, this);
bool isFormatVBR = (mDataFormat.mBytesPerPacket == 0 || mDataFormat.mFramesPerPacket == 0);
AudioQueueSetParameter(mQueue, kAudioQueueParam_Volume, 1.0);
for (int i = 0; i < kNumberBuffers; ++i) {
AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers[i]);
}
// set the volume of the queue
mIsInitialized = true;
}
void AQPlayer::DisposeQueue(Boolean inDisposeFile)
{
if (mQueue)
{
AudioQueueDispose(mQueue, true);
mQueue = NULL;
}
if (inDisposeFile)
{
if (mAudioFile)
{
AudioFileClose(mAudioFile);
mAudioFile = 0;
}
if (mFilePath)
{
CFRelease(mFilePath);
mFilePath = NULL;
}
}
mIsInitialized = false;
}
Thank for your time.
Your input is VBR so your buffers need an accompanying packet description array. Use AudioQueueAllocateBufferWithPacketDescriptions: instead of AudioQueueAllocateBuffer: when you are initially creating your buffers. Then set mStartOffset, mDataByteSize, and mVariableFramesInPacket (always zero) for each packet inside your while loop in your callback procedure.
I have a requirement to parse data files in "txf" format. The files may contain more than 1000 entries. Since the format is well defined like JSON, I wanted to make a generic parser like JSON, which can serialise and deserialise txf files.
On contrary to JSON, the mark up doesn't have a way to identify an object or an array. If an entry with same tag occurs, we need to consider it as an array.
# Marks the start of an object.
$ Marks the members of an object
/ Marks the end of an object
Following is a sample "txf" file
#Employees
$LastUpdated=2015-02-01 14:01:00
#Employee
$Id=1
$Name=Employee 01
#Departments
$LastUpdated=2015-02-01 14:01:00
#Department
$Id=1
$Name=Department Name
/Department
/Departments
/Employee
#Employee
/Employee
/Employees
I was able to create a generic TXF Parser using NSScanner. But with more entries the performance needs more tweaking.
I wrote the foundation object obtained as plist and compared its performance again the parser I wrote. My parser is around 10 times slower than plist parser.
While plist file size is 5 times more than txf and has more markup characters, I feel that there is a lot of room for optimization.
Any help in that direction is highly appreciated.
EDIT : Including the parsing code
static NSString *const kArray = #"TXFArray";
static NSString *const kBodyText = #"TXFText";
#interface TXFParser ()
/*Temporary variable to hold values of an object*/
#property (nonatomic, strong) NSMutableDictionary *dict;
/*An array to hold the hierarchial data of all nodes encountered while parsing*/
#property (nonatomic, strong) NSMutableArray *stack;
#end
#implementation TXFParser
#pragma mark - Getters
- (NSMutableArray *)stack{
if (!_stack) {
_stack = [NSMutableArray new];
}return _stack;
}
#pragma mark -
- (id)objectFromString:(NSString *)txfString{
[txfString enumerateLinesUsingBlock:^(NSString *string, BOOL *stop) {
if ([string hasPrefix:#"#"]) {
[self didStartParsingTag:[string substringFromIndex:1]];
}else if([string hasPrefix:#"$"]){
[self didFindKeyValuePair:[string substringFromIndex:1]];
}else if([string hasPrefix:#"/"]){
[self didEndParsingTag:[string substringFromIndex:1]];
}else{
//[self didFindBodyValue:string];
}
}]; return self.dict;
}
#pragma mark -
- (void)didStartParsingTag:(NSString *)tag{
[self parserFoundObjectStartForKey:tag];
}
- (void)didFindKeyValuePair:(NSString *)tag{
NSArray *components = [tag componentsSeparatedByString:#"="];
NSString *key = [components firstObject];
NSString *value = [components lastObject];
if (key.length) {
self.dict[key] = value?:#"";
}
}
- (void)didFindBodyValue:(NSString *)bodyString{
if (!bodyString.length) return;
bodyString = [bodyString stringByTrimmingCharactersInSet:[NSCharacterSet illegalCharacterSet]];
if (!bodyString.length) return;
self.dict[kBodyText] = bodyString;
}
- (void)didEndParsingTag:(NSString *)tag{
[self parserFoundObjectEndForKey:tag];
}
#pragma mark -
- (void)parserFoundObjectStartForKey:(NSString *)key{
self.dict = [NSMutableDictionary new];
[self.stack addObject:self.dict];
}
- (void)parserFoundObjectEndForKey:(NSString *)key{
NSDictionary *dict = self.dict;
//Remove the last value of stack
[self.stack removeLastObject];
//Load the previous object as dict
self.dict = [self.stack lastObject];
//The stack has contents, then we need to append objects
if ([self.stack count]) {
[self addObject:dict forKey:key];
}else{
//This is root object,wrap with key and assign output
self.dict = (NSMutableDictionary *)[self wrapObject:dict withKey:key];
}
}
#pragma mark - Add Objects after finding end tag
- (void)addObject:(id)dict forKey:(NSString *)key{
//If there is no value, bailout
if (!dict) return;
//Check if the dict already has a value for key array.
NSMutableArray *array = self.dict[kArray];
//If array key is not found look for another object with same key
if (array) {
//Array found add current object after wrapping with key
NSDictionary *currentDict = [self wrapObject:dict withKey:key];
[array addObject:currentDict];
}else{
id prevObj = self.dict[key];
if (prevObj) {
/*
There is a prev value for the same key. That means we need to wrap that object in a collection.
1. Remove the object from dictionary,
2. Wrap it with its key
3. Add the prev and current value to array
4. Save the array back to dict
*/
[self.dict removeObjectForKey:key];
NSDictionary *prevDict = [self wrapObject:prevObj withKey:key];
NSDictionary *currentDict = [self wrapObject:dict withKey:key];
self.dict[kArray] = [#[prevDict,currentDict] mutableCopy];
}else{
//Simply add object to dict
self.dict[key] = dict;
}
}
}
/*Wraps Object with a key for the serializer to generate txf tag*/
- (NSDictionary *)wrapObject:(id)obj withKey:(NSString *)key{
if (!key ||!obj) {
return #{};
}
return #{key:obj};
}
EDIT 2:
A sample TXF file with more than 1000 entries.
Have you considered using pull-style reads & recursive processing? That would eliminate reading the whole file into memory and also eliminate managing some own stack to keep track how deep you're parsing.
Below an example in Swift. The example works with your sample "txf", but not with the dropbox version; some of your "members" span over multiple lines. If this is a requirement, it can easily be implemented into switch/case "$" section. However, I don't see your own code handling this either. Also, the example doesn't follow the correct Swift error handling yet (the parse method would need an additional NSError parameter)
import Foundation
extension String
{
public func indexOfCharacter(char: Character) -> Int? {
if let idx = find(self, char) {
return distance(self.startIndex, idx)
}
return nil
}
func substringToIndex(index:Int) -> String {
return self.substringToIndex(advance(self.startIndex, index))
}
func substringFromIndex(index:Int) -> String {
return self.substringFromIndex(advance(self.startIndex, index))
}
}
func parse(aStreamReader:StreamReader, parentTagName:String) -> Dictionary<String,AnyObject> {
var dict = Dictionary<String,AnyObject>()
while let line = aStreamReader.nextLine() {
let firstChar = first(line)
let theRest = dropFirst(line)
switch firstChar! {
case "$":
if let idx = theRest.indexOfCharacter("=") {
let key = theRest.substringToIndex(idx)
let value = theRest.substringFromIndex(idx+1)
dict[key] = value
} else {
println("no = sign")
}
case "#":
let subDict = parse(aStreamReader,theRest)
var list = dict[theRest] as? [Dictionary<String,AnyObject>]
if list == nil {
dict[theRest] = [subDict]
} else {
list!.append(subDict)
}
case "/":
if theRest != parentTagName {
println("mismatch... [\(theRest)] != [\(parentTagName)]")
} else {
return dict
}
default:
println("mismatch... [\(line)]")
}
}
println("shouldn't be here...")
return dict
}
var data : Dictionary<String,AnyObject>?
if let aStreamReader = StreamReader(path: "/Users/taoufik/Desktop/QuickParser/QuickParser/file.txf") {
if var line = aStreamReader.nextLine() {
let tagName = line.substringFromIndex(advance(line.startIndex, 1))
data = parse(aStreamReader, tagName)
}
aStreamReader.close()
}
println(JSON(data!))
And the StreamReader was borrowed from https://stackoverflow.com/a/24648951/95976
Edit
see full code https://github.com/tofi9/QuickParser
pull-style line-by-line read in objective-c: How to read data from NSFileHandle line by line?
Edit 2
I rewrote the above in C++11 and got it to run in less than 0.05 seconds (release mode) on a 2012 MBA I5 using the updated file on dropbox. I suspect NSDictionary and NSArray must have some penalty. The code below can be compiled into an objective-c project (file needs have extension .mm):
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <map>
#include <vector>
using namespace std;
class benchmark {
private:
typedef std::chrono::high_resolution_clock clock;
typedef std::chrono::milliseconds milliseconds;
clock::time_point start;
public:
benchmark(bool startCounting = true) {
if(startCounting)
start = clock::now();
}
void reset() {
start = clock::now();
}
double elapsed() {
milliseconds ms = std::chrono::duration_cast<milliseconds>(clock::now() - start);
double elapsed_secs = ms.count() / 1000.0;
return elapsed_secs;
}
};
struct obj {
map<string,string> properties;
map<string,vector<obj>> subObjects;
};
obj parse(ifstream& stream, string& parentTagName) {
obj obj;
string line;
while (getline(stream, line))
{
auto firstChar = line[0];
auto rest = line.substr(1);
switch (firstChar) {
case '$': {
auto idx = rest.find_first_of('=');
if (idx == -1) {
ostringstream o;
o << "no = sign: " << line;
throw o.str();
}
auto key = rest.substr(0,idx);
auto value = rest.substr(idx+1);
obj.properties[key] = value;
break;
}
case '#': {
auto subObj = parse(stream, rest);
obj.subObjects[rest].push_back(subObj);
break;
}
case '/':
if(rest != parentTagName) {
ostringstream o;
o << "mismatch end of object " << rest << " != " << parentTagName;
throw o.str();
} else {
return obj;
}
break;
default:
ostringstream o;
o << "mismatch line " << line;
throw o.str();
break;
}
}
throw "I don't know why I'm here. Probably because the file is missing an end of object marker";
}
void visualise(obj& obj, int indent = 0) {
for(auto& property : obj.properties) {
cout << string(indent, '\t') << property.first << " = " << property.second << endl;
}
for(auto& subObjects : obj.subObjects) {
for(auto& subObject : subObjects.second) {
cout << string(indent, '\t') << subObjects.first << ": " << endl;
visualise(subObject, indent + 1);
}
}
}
int main(int argc, const char * argv[]) {
try {
obj result;
benchmark b;
ifstream stream("/Users/taoufik/Desktop/QuickParser/QuickParser/Members.txf");
string line;
if (getline(stream, line))
{
string tagName = line.substr(1);
result = parse(stream, tagName);
}
cout << "elapsed " << b.elapsed() << " ms" << endl;
visualise(result);
}catch(string s) {
cout << "error " << s;
}
return 0;
}
Edit 3
See link for full code C++: https://github.com/tofi9/TxfParser
I did some work on your github source - with following 2 changes I got overal improvement of 30% though the major improvement is from "Optimisation 1"
Optimisation 1 - based on your data came with with following work.
+ (int)locate:(NSString*)inString check:(unichar) identifier
{
int ret = -1;
for (int i = 0 ; i < inString.length; i++){
if (identifier == [inString characterAtIndex:i]) {
ret = i;
break;
}
}
return ret;
}
- (void)didFindKeyValuePair:(NSString *)tag{
#if 0
NSArray *components = [tag componentsSeparatedByString:#"="];
NSString *key = [components firstObject];
NSString *value = [components lastObject];
#else
int locate = [TXFParser locate:tag check:'='];
NSString *key = [tag substringToIndex:locate];
NSString *value = [tag substringFromIndex:locate+1];
#endif
if (key.length) {
self.dict[key] = value?:#"";
}
}
Optimisation 2:
- (id)objectFromString:(NSString *)txfString{
[txfString enumerateLinesUsingBlock:^(NSString *string, BOOL *stop) {
#if 0
if ([string hasPrefix:#"#"]) {
[self didStartParsingTag:[string substringFromIndex:1]];
}else if([string hasPrefix:#"$"]){
[self didFindKeyValuePair:[string substringFromIndex:1]];
}else if([string hasPrefix:#"/"]){
[self didEndParsingTag:[string substringFromIndex:1]];
}else{
//[self didFindBodyValue:string];
}
#else
unichar identifier = ([string length]>0)?[string characterAtIndex:0]:0;
if (identifier == '#') {
[self didStartParsingTag:[string substringFromIndex:1]];
}else if(identifier == '$'){
[self didFindKeyValuePair:[string substringFromIndex:1]];
}else if(identifier == '/'){
[self didEndParsingTag:[string substringFromIndex:1]];
}else{
//[self didFindBodyValue:string];
}
#endif
}]; return self.dict;
}
Hope it helps you.
I want to tweet corresponding to a situation which means I need to tweet many times. I did like this:
void loop() {
checkState();
}
void tweet() {
Serial.println("Connecting to Twitter");
if (twitter.post("input is greater than 5")) {
Serial.print(dataString);
int status = twitter.wait();
if (status == 200) {
Serial.println("Successful");
} else {
Serial.println("Tweet failed.");
Serial.println(status);
}
} else {
Serial.println("Connection to Twitter failed");
}
Serial.println("30 Seconds timeout started");
delay(3000);
}
void checkState() {
// read input and append to the string:
char analogPin = 0;
char input = analogRead(analogPin);
dataString = input;
if (input >= 1023) {
tweet();
delay(2000);
}
}
But it doesn't work. Do you have any suggestion?
I have this:
-(ALuint)ID
{ return self.soundSource->_sourceId; }
-(void)play
{
[self.engine playSound:self.soundSource.soundId
sourceGroupId:0
pitch:self.pitch
pan:1.0
gain:1.0
loop:NO];
}
-(NSTimeInterval)duration
{ return durationOfSourceId(self.ID); }
-(NSTimeInterval)offset
{ return elapsedTimeOfSourceId(self.ID); }
-(BOOL)isPlaying
{
NSTimeInterval secondsRemaining = self.duration - self.offset;
NSLog(#"<%.3f>", self.soundSource.durationInSeconds);
NSLog(#"<%.3f> - <%.3f> = <%.3f> isPlaying <%i>", self.duration, self.offset, secondsRemaining, self.soundSource.isPlaying);
return (secondsRemaining > 0.0);
}
#pragma mark - OpenAL addons
static NSTimeInterval elapsedTimeOfSourceId(ALuint sourceID)
{
float result = 0.0;
alGetSourcef(sourceID, AL_SEC_OFFSET, &result);
return result;
}
static NSTimeInterval durationOfSourceId(ALuint sourceID)
{
//Thanks to http://stackoverflow.com/a/8822347
ALint bufferID, bufferSize, frequency, bitsPerSample, channels;
alGetSourcei(sourceID, AL_BUFFER, &bufferID);
alGetBufferi(bufferID, AL_SIZE, &bufferSize);
alGetBufferi(bufferID, AL_FREQUENCY, &frequency);
alGetBufferi(bufferID, AL_CHANNELS, &channels);
alGetBufferi(bufferID, AL_BITS, &bitsPerSample);
NSTimeInterval result = ((double)bufferSize)/(frequency*channels*(bitsPerSample/8));
return result;
}
Where engine is just an instance of CDSoundEngine. I really want to know when will the music stop. I'm into it for a whole day now, and I'm tired.
It logs:
[1445:707] <1.656>
[1445:707] <1.656> - <0.000> = <1.656> isPlaying <0>
So the OpenAL source ID is right (since I can get the duration).
The CDSoundSource is also right (since I can get the duration from that as well).
I can hear the sound playing.
But AL_SEC_OFFSET is always 0.0, isPlaying is always NO.
Why dont you get the state of the source and check if it is really being played?:
alGetSourcei(source, AL_SOURCE_STATE, &state);
return (state == AL_PLAYING);