diff --git a/VideoCore.podspec b/VideoCore.podspec index 6439cc44..4e1f1c33 100644 --- a/VideoCore.podspec +++ b/VideoCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "VideoCore" - s.version = "0.1.8.4" + s.version = "0.1.8.5" s.summary = "An audio and video manipulation pipeline for iOS and Mac OS X." s.description = <<-DESC This is a work-in-progress library with the @@ -22,12 +22,12 @@ Pod::Spec.new do |s| 'sources/**/*.h*', 'sources/**/*.cpp', 'sources/**/*.m*', 'stream/**/*.h*', 'stream/**/*.cpp', 'stream/**/*.m*', 'system/**/*.h*', 'system/**/*.cpp', 'system/**/*.m*', - 'transforms/**/*.h*', 'transforms/**/*.cpp', 'transforms/**/*.m*', + 'transforms/**/*.h*', 'transforms/**/*.cpp', 'transforms/**/*.m*', 'api/**/*.h*', 'api/**/*.m*' ] s.frameworks = [ 'VideoToolbox', 'AudioToolbox', 'AVFoundation', 'CFNetwork', 'CoreMedia', 'CoreVideo', 'OpenGLES', 'Foundation', 'CoreGraphics' ] - + s.libraries = 'c++' s.dependency 'boost', '~> 1.51.0' diff --git a/api/iOS/VCSimpleSession.h b/api/iOS/VCSimpleSession.h index 9ffee7f5..8871b0fc 100644 --- a/api/iOS/VCSimpleSession.h +++ b/api/iOS/VCSimpleSession.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,11 +20,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ /*! - * A simple Objective-C Session API that will create an RTMP session using the + * A simple Objective-C Session API that will create an RTMP session using the * device's camera(s) and microphone. * */ @@ -41,7 +41,7 @@ typedef NS_ENUM(NSInteger, VCSessionState) VCSessionStateStarted, VCSessionStateEnded, VCSessionStateError - + }; typedef NS_ENUM(NSInteger, VCCameraState) @@ -67,6 +67,8 @@ typedef NS_ENUM(NSInteger, VCCameraState) @property (nonatomic, assign) VCCameraState cameraState; @property (nonatomic, assign) BOOL torch; @property (nonatomic, assign) float videoZoomFactor; +@property (nonatomic, assign) int audioChannelCount; +@property (nonatomic, assign) float audioSampleRate; @property (nonatomic, assign) float micGain; // [0..1] @property (nonatomic, assign) id delegate; diff --git a/api/iOS/VCSimpleSession.mm b/api/iOS/VCSimpleSession.mm index cf17112f..0e88a745 100644 --- a/api/iOS/VCSimpleSession.mm +++ b/api/iOS/VCSimpleSession.mm @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ of this software and associated documentation files (the "Software"), to deal LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #import @@ -57,25 +57,25 @@ of this software and associated documentation files (the "Software"), to deal #include namespace videocore { namespace simpleApi { - + using PixelBufferCallback = std::function ; - + class PixelBufferOutput : public IOutput { public: PixelBufferOutput(PixelBufferCallback callback) : m_callback(callback) {}; - + void pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata) { m_callback(data, size); } - + private: - + PixelBufferCallback m_callback; }; } @@ -83,13 +83,13 @@ void pushBuffer(const uint8_t* const data, @interface VCSimpleSession() { - + VCPreviewView* _previewView; - + std::shared_ptr m_pbOutput; std::shared_ptr m_micSource; std::shared_ptr m_cameraSource; - + std::shared_ptr m_videoSplit; std::shared_ptr m_aspectTransform; std::shared_ptr m_positionTransform; @@ -99,21 +99,23 @@ @interface VCSimpleSession() std::shared_ptr m_aacEncoder; std::shared_ptr m_h264Packetizer; std::shared_ptr m_aacPacketizer; - + std::shared_ptr m_aacSplit; std::shared_ptr m_h264Split; std::shared_ptr m_muxer; - + std::shared_ptr m_outputSession; // properties - + CGSize _videoSize; int _bitrate; int _fps; float _videoZoomFactor; + int _audioChannelCount; + float _audioSampleRate; float _micGain; - + VCCameraState _cameraState; VCSessionState _rtmpSessionState; BOOL _torch; @@ -125,8 +127,6 @@ - (void) setupGraph; @end -static const float kAudioRate = 44100; - @implementation VCSimpleSession @dynamic videoSize; @dynamic bitrate; @@ -135,6 +135,8 @@ @implementation VCSimpleSession @dynamic cameraState; @dynamic rtmpSessionState; @dynamic videoZoomFactor; +@dynamic audioChannelCount; +@dynamic audioSampleRate; @dynamic micGain; @dynamic previewView; @@ -222,6 +224,28 @@ - (void) setVideoZoomFactor:(float)videoZoomFactor self.videoSize.height * videoZoomFactor); } } +- (void) setAudioChannelCount:(int)channelCount +{ + _audioChannelCount = channelCount; + if(m_audioMixer) { + m_audioMixer->setChannelCount(channelCount); + } +} +- (int) audioChannelCount +{ + return _audioChannelCount; +} +- (void) setAudioSampleRate:(float)sampleRate +{ + _audioSampleRate = sampleRate; + if(m_audioMixer) { + m_audioMixer->setFrequencyInHz(sampleRate); + } +} +- (float) audioSampleRate +{ + return _audioSampleRate; +} - (void) setMicGain:(float)micGain { if(m_audioMixer) { @@ -241,6 +265,7 @@ - (UIView*) previewView { // Public Methods // ----------------------------------------------------------------------------- #pragma mark - Public Methods +// ----------------------------------------------------------------------------- - (instancetype) initWithVideoSize:(CGSize)videoSize frameRate:(int)fps bitrate:(int)bps @@ -251,12 +276,14 @@ - (instancetype) initWithVideoSize:(CGSize)videoSize self.videoSize = videoSize; self.fps = fps; self.micGain = 1.f; - + self.audioChannelCount = 2; + self.audioSampleRate = 44100.; + _previewView = [[VCPreviewView alloc] init]; self.videoZoomFactor = 1.f; - + _cameraState = VCCameraStateBack; - + dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self setupGraph]; }); @@ -276,10 +303,10 @@ - (void) dealloc m_micSource.reset(); m_cameraSource.reset(); m_pbOutput.reset(); - + [_previewView release]; _previewView = nil; - + [super dealloc]; } @@ -288,14 +315,14 @@ - (void) startRtmpSessionWithURL:(NSString *)rtmpUrl { std::stringstream uri ; uri << [rtmpUrl UTF8String] << "/" << [streamKey UTF8String]; - + m_outputSession.reset( new videocore::RTMPSession ( uri.str(), [=](videocore::RTMPSession& session, ClientState_t state) { - + switch(state) { - + case kClientStateConnected: self.rtmpSessionState = VCSessionStateStarting; break; @@ -313,18 +340,19 @@ - (void) startRtmpSessionWithURL:(NSString *)rtmpUrl break; default: break; - + } - + }) ); videocore::RTMPSessionParameters_t sp ( 0. ); - + sp.setData(self.videoSize.width, self.videoSize.height, 1. / static_cast(self.fps), self.bitrate, - kAudioRate); - + self.audioSampleRate, + (self.audioChannelCount == 2)); + m_outputSession->setSessionParameters(sp); } - (void) endRtmpSession @@ -335,9 +363,9 @@ - (void) endRtmpSession m_videoSplit->removeOutput(m_h264Encoder); m_h264Encoder.reset(); m_aacEncoder.reset(); - + m_outputSession.reset(); - + self.rtmpSessionState = VCSessionStateEnded; } @@ -350,38 +378,38 @@ - (void) endRtmpSession - (void) setupGraph { const double frameDuration = 1. / static_cast(self.fps); - + { // Add audio mixer - const double aacPacketTime = 1024. / kAudioRate; - - m_audioMixer = std::make_shared(2, - kAudioRate, + const double aacPacketTime = 1024. / self.audioSampleRate; + + m_audioMixer = std::make_shared(self.audioChannelCount, + self.audioSampleRate, 16, aacPacketTime); - + // The H.264 Encoder introduces about 2 frames of latency, so we will set the minimum audio buffer duration to 2 frames. m_audioMixer->setMinimumBufferDuration(frameDuration*2); } #ifdef __APPLE__ #ifdef TARGET_OS_IPHONE - - + + { // Add video mixer m_videoMixer = std::make_shared(self.videoSize.width, self.videoSize.height, frameDuration); - + } - + { auto videoSplit = std::make_shared(); - + m_videoSplit = videoSplit; VCPreviewView* preview = (VCPreviewView*)self.previewView; - + m_pbOutput = std::make_shared([=](const void* const data, size_t size){ CVPixelBufferRef ref = (CVPixelBufferRef)data; [preview drawFrame:ref]; @@ -389,27 +417,27 @@ - (void) setupGraph self.rtmpSessionState = VCSessionStatePreviewStarted; } }); - + videoSplit->setOutput(m_pbOutput); m_videoMixer->setOutput(videoSplit); - + } - + #else #endif // TARGET_OS_IPHONE #endif // __APPLE__ - + // Create sources { // Add camera source m_cameraSource = std::make_shared(); auto aspectTransform = std::make_shared(self.videoSize.width,self.videoSize.height,videocore::AspectTransform::kAspectFit); - + auto positionTransform = std::make_shared(self.videoSize.width/2, self.videoSize.height/2, self.videoSize.width, self.videoSize.height, self.videoSize.width, self.videoSize.height ); - + std::dynamic_pointer_cast(m_cameraSource)->setupCamera(self.fps,false); m_cameraSource->setOutput(aspectTransform); aspectTransform->setOutput(positionTransform); @@ -419,18 +447,18 @@ - (void) setupGraph } { // Add mic source - m_micSource = std::make_shared(); - //m_micSource->setOutput(m_audioMixer); - - + m_micSource = std::make_shared(self.audioSampleRate, self.audioChannelCount); + m_micSource->setOutput(m_audioMixer); + + } } - (void) addEncodersAndPacketizers { { // Add encoders - - m_aacEncoder = std::make_shared(kAudioRate,2); + + m_aacEncoder = std::make_shared(self.audioSampleRate, self.audioChannelCount); if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { // If >= iOS 8.0 use the VideoToolbox encoder that does not write to disk. m_h264Encoder = std::make_shared(self.videoSize.width, @@ -443,9 +471,9 @@ - (void) addEncodersAndPacketizers self.fps, self.bitrate); } - //m_audioMixer->setOutput(m_aacEncoder); + m_audioMixer->setOutput(m_aacEncoder); m_videoSplit->setOutput(m_h264Encoder); - + } { m_aacSplit = std::make_shared(); @@ -455,11 +483,11 @@ - (void) addEncodersAndPacketizers } { m_h264Packetizer = std::make_shared(); - m_aacPacketizer = std::make_shared(); - + m_aacPacketizer = std::make_shared(self.audioSampleRate, self.audioChannelCount); + m_h264Split->setOutput(m_h264Packetizer); m_aacSplit->setOutput(m_aacPacketizer); - + } { /*m_muxer = std::make_shared(); @@ -471,14 +499,14 @@ - (void) addEncodersAndPacketizers m_h264Split->setOutput(m_muxer);*/ } const auto epoch = std::chrono::steady_clock::now(); - + m_audioMixer->setEpoch(epoch); m_videoMixer->setEpoch(epoch); - + m_h264Packetizer->setOutput(m_outputSession); m_aacPacketizer->setOutput(m_outputSession); - + } - (NSString *) applicationDocumentsDirectory { diff --git a/mixers/GenericAudioMixer.cpp b/mixers/GenericAudioMixer.cpp index 72bc863b..81712539 100644 --- a/mixers/GenericAudioMixer.cpp +++ b/mixers/GenericAudioMixer.cpp @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #include #include @@ -42,7 +42,7 @@ static inline int16_t b32_to_b16(void* v) { static inline int16_t b24_to_b16(void* v) { static const int m = 1U << 23; - + uint8_t* p = (uint8_t*)v; union { @@ -53,11 +53,11 @@ static inline int16_t b24_to_b16(void* v) { in[1] = p[1]; in[2] = p[2]; in[3] = 0; - + int32_t r = (x ^ m) - m; - + return b32_to_b16(&r); - + } extern std::string g_tmpFolder; @@ -69,15 +69,15 @@ namespace videocore { double mu) { double a[4], mu2; - + mu2 = mu*mu; a[0] = y3 - y2 - y0 + y1; a[1] = y0 - y1 - a[0]; a[2] = y2 - y0; a[3] = y1; - + return (a[0]*mu*mu2+a[1]*mu2+a[2]*mu+a[3]); - + } GenericAudioMixer::GenericAudioMixer(int outChannelCount, int outFrequencyInHz, @@ -85,13 +85,13 @@ namespace videocore { double frameDuration) : m_bufferDuration(frameDuration), m_frameDuration(frameDuration), - m_outChannelCount(2), + m_outChannelCount(outChannelCount), m_outFrequencyInHz(outFrequencyInHz), m_outBitsPerChannel(16), m_exiting(false) { m_bytesPerSample = outChannelCount * outBitsPerChannel / 8; - + m_mixThread = std::thread([this]() { pthread_setname_np("com.videocore.audiomixer"); this->mixThread(); @@ -114,9 +114,9 @@ namespace videocore { { auto hash = std::hash >()(source); size_t bufferSize = (inBufferSize ? inBufferSize : (m_bytesPerSample * m_outFrequencyInHz * m_bufferDuration * 4)); // 4 frames of buffer space. - + std::unique_ptr buffer(new RingBuffer(bufferSize)); - + m_inBuffer[hash] = std::move(buffer); m_inGain[hash] = 1.f; } @@ -124,7 +124,7 @@ namespace videocore { GenericAudioMixer::unregisterSource(std::shared_ptr source) { auto hash = std::hash >()(source); - + auto it = m_inBuffer.find(hash); if(it != m_inBuffer.end()) { @@ -141,27 +141,27 @@ namespace videocore { IMetadata& metadata) { AudioBufferMetadata & inMeta = static_cast(metadata); - + if(inMeta.size() >= 5) { const auto inSource = inMeta.getData() ; - + auto lSource = inSource.lock(); if(lSource) { - + auto hash = std::hash > ()(lSource); - + auto ret = resample(data, size, inMeta); if(ret->size() > 0) { // push buffer uint8_t *p; size_t rsize = ret->read(&p, ret->size()); m_inBuffer[hash]->put(p, rsize); - + } else { // use data provided m_inBuffer[hash]->put(const_cast(data), size); } - + } } } @@ -174,15 +174,15 @@ namespace videocore { const auto inBitsPerChannel = metadata.getData(); const auto inChannelCount = metadata.getData(); //auto inLoops = metadata.getData(); - + if(m_outFrequencyInHz == inFrequncyInHz && m_outBitsPerChannel == inBitsPerChannel && m_outChannelCount == inChannelCount) { // No resampling necessary return std::make_shared(); } - + int16_t (*bitconvert)(void* val) = NULL; - + switch(inBitsPerChannel) { case 8: @@ -201,47 +201,47 @@ namespace videocore { bitconvert = b16_to_b16; break; } - + const size_t bytesPerChannel = inBitsPerChannel / 8; const size_t bytesPerSample = inChannelCount * bytesPerChannel; - + const double ratio = static_cast(inFrequncyInHz) / static_cast(m_outFrequencyInHz); - + const size_t sampleCount = size / bytesPerSample; const size_t outSampleCount = sampleCount / ratio; const size_t outBufferSize = outSampleCount * m_bytesPerSample; - + const auto outBuffer = std::make_shared(outBufferSize); - + uint8_t * pOutBuffer = NULL; uint8_t * pInBuffer = const_cast(buffer); outBuffer->read(&pOutBuffer, outBufferSize); int16_t* currSample = (int16_t*)(&pOutBuffer[0]); - + double currentInByteOffset = 0.; const double sampleStride = ratio * static_cast(bytesPerSample); - + const size_t channelStride = (inChannelCount > 1) * bytesPerChannel; - + for( size_t i = 0 ; i < outSampleCount ; ++i ) { size_t iSample = (static_cast(std::floor(currentInByteOffset)) + (bytesPerSample-1)) & ~(bytesPerSample-1); // get an aligned sample. - - + + currentInByteOffset += sampleStride; - + pInBuffer = (const_cast(buffer)+iSample); - + int16_t sampleL = bitconvert(pInBuffer); int16_t sampleR = bitconvert(pInBuffer + channelStride); - + *currSample++ = sampleL; *currSample++ = sampleR; - + } outBuffer->setSize(outBufferSize); return outBuffer; @@ -258,12 +258,22 @@ namespace videocore { auto s = source.lock(); if(s) { auto hash = std::hash>()(s); - + m_inGain[hash] = std::max(0.f, std::min(1.f, gain)); - + } } - + void + GenericAudioMixer::setChannelCount(int channelCount) + { + m_outChannelCount = channelCount; + } + void + GenericAudioMixer::setFrequencyInHz(float frequencyInHz) + { + m_outFrequencyInHz = frequencyInHz; + } + void GenericAudioMixer::mixThread() { @@ -271,29 +281,29 @@ namespace videocore { const float g = 0.70710678118f; // 1 / sqrt(2) const size_t outSampleCount = static_cast(m_outFrequencyInHz * m_frameDuration); const size_t outBufferSize = outSampleCount * m_bytesPerSample ; - + const size_t requiredSampleCount = static_cast(m_outFrequencyInHz * m_bufferDuration); const size_t requiredBufferSize = requiredSampleCount * m_bytesPerSample; - + const std::unique_ptr buffer(new short[outBufferSize / sizeof(short)]); const std::unique_ptr samples(new short[outBufferSize / sizeof(short)]); - + m_nextMixTime = std::chrono::steady_clock::now(); - + while(!m_exiting.load()) { std::unique_lock l(m_mixMutex); - + const auto now = std::chrono::steady_clock::now(); if( now >= m_nextMixTime) { - + size_t sampleBufferSize = 0; m_nextMixTime += us; - + // Mix and push for ( auto it = m_inBuffer.begin() ; it != m_inBuffer.end() ; ++it ) { - + // // TODO: A better approach is to put the buffer size requirement on the OUTPUT buffer, and not on the input buffers. // @@ -302,11 +312,11 @@ namespace videocore { if(size > sampleBufferSize) { sampleBufferSize = size; } - + const size_t count = (size/sizeof(short)); const float gain = m_inGain[it->first]; const float mult = g*gain; - + for ( size_t i = 0 ; i < count ; i+=8) { samples[i] += buffer[i] * mult; samples[i+1] += buffer[i+1] * mult; @@ -319,20 +329,20 @@ namespace videocore { } } } - + if(sampleBufferSize) { - + AudioBufferMetadata md ( std::chrono::duration_cast(m_nextMixTime - m_epoch).count() ); std::shared_ptr blank; - + md.setData(m_outFrequencyInHz, m_outBitsPerChannel, m_outChannelCount, false, blank); - + auto out = m_output.lock(); if(out) { out->pushBuffer((uint8_t*)&samples[0], sampleBufferSize, md); } } - + memset(samples.get(), 0, outBufferSize); } m_mixThreadCond.wait_until(l, m_nextMixTime); diff --git a/mixers/GenericAudioMixer.h b/mixers/GenericAudioMixer.h index 2b3c986d..d87b55fd 100644 --- a/mixers/GenericAudioMixer.h +++ b/mixers/GenericAudioMixer.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #ifndef __videocore__GenericAudioMixer__ @@ -34,7 +34,7 @@ #include namespace videocore { - + /*! * Basic, cross-platform mixer that uses a very simple nearest neighbour resampling method * and the sum of the samples to mix. The mixer takes LPCM data from multiple sources, resamples (if needed), and @@ -46,7 +46,7 @@ namespace videocore { */ class GenericAudioMixer : public IAudioMixer { - + public: /*! * Constructor. @@ -61,42 +61,48 @@ namespace videocore { int outFrequencyInHz, int outBitsPerChannel, double frameDuration); - + /*! Destructor */ ~GenericAudioMixer(); - + public: /*! IMixer::registerSource */ void registerSource(std::shared_ptr source, size_t inBufferSize = 0) ; - + /*! IMixer::unregisterSource */ void unregisterSource(std::shared_ptr source); - + /*! IOutput::pushBuffer */ void pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata); - + /*! ITransform::setOutput */ void setOutput(std::shared_ptr output); - + /*! IAudioMixer::setSourceGain */ void setSourceGain(std::weak_ptr source, float gain); - + + /*! IAudioMixer::setChannelCount */ + void setChannelCount(int channelCount); + + /*! IAudioMixer::setFrequencyInHz */ + void setFrequencyInHz(float frequencyInHz); + /*! IAudioMixer::setMinimumBufferDuration */ virtual void setMinimumBufferDuration(const double duration) ; - + /*! ITransform::setEpoch */ void setEpoch(const std::chrono::steady_clock::time_point epoch) { m_epoch = epoch; m_nextMixTime = epoch; }; - + protected: - - /*! + + /*! * Called to resample a buffer of audio samples. * * \param buffer The input samples @@ -108,34 +114,34 @@ namespace videocore { virtual std::shared_ptr resample(const uint8_t* const buffer, size_t size, AudioBufferMetadata& metadata); - + /*! * Start the mixer thread. */ void mixThread(); - + protected: std::chrono::steady_clock::time_point m_epoch; std::chrono::steady_clock::time_point m_nextMixTime; - + double m_frameDuration; double m_bufferDuration; - + std::thread m_mixThread; std::mutex m_mixMutex; std::condition_variable m_mixThreadCond; - + std::weak_ptr m_output; std::map < std::size_t, std::unique_ptr > m_inBuffer; std::map < std::size_t, float > m_inGain; - + int m_outChannelCount; int m_outFrequencyInHz; int m_outBitsPerChannel; int m_bytesPerSample; - + std::atomic m_exiting; - + }; } #endif /* defined(__videocore__GenericAudioMixer__) */ diff --git a/mixers/IAudioMixer.hpp b/mixers/IAudioMixer.hpp index 99f1dafc..53dbcaf3 100644 --- a/mixers/IAudioMixer.hpp +++ b/mixers/IAudioMixer.hpp @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #ifndef videocore_IAudioMixer_hpp #define videocore_IAudioMixer_hpp @@ -30,7 +30,7 @@ #include namespace videocore { - + /*! Enum values for the AudioBufferMetadata tuple */ enum { @@ -40,23 +40,23 @@ namespace videocore { kAudioMetadataLoops, /*!< Indicates whether or not the buffer should loop. Currently ignored. */ kAudioMetadataSource /*!< A smart pointer to the source. */ }; - + /*! * Specifies the properties of the incoming audio buffer. */ typedef MetaData<'soun', int, int, int, bool, std::weak_ptr > AudioBufferMetadata; - + class ISource; - + /*! IAudioMixer interface. Defines the required interface methods for Audio mixers. */ class IAudioMixer : public IMixer { public: - + /*! Virtual destructor */ virtual ~IAudioMixer() {}; - - /*! + + /*! * Set the output gain of the specified source. * * \param source A smart pointer to the source to be modified @@ -64,7 +64,21 @@ namespace videocore { */ virtual void setSourceGain(std::weak_ptr source, float gain) = 0; - + + /*! + * Set the channel count. + * + * \param channelCount The number of audio channels. + */ + virtual void setChannelCount(int channelCount) = 0; + + /*! + * Set the channel count. + * + * \param frequencyInHz The audio sample frequency in Hz. + */ + virtual void setFrequencyInHz(float frequencyInHz) = 0; + /*! * Set the amount of time to buffer before emitting mixed samples. * diff --git a/rtmp/RTMPSession.cpp b/rtmp/RTMPSession.cpp index 0efcab99..b641e53c 100644 --- a/rtmp/RTMPSession.cpp +++ b/rtmp/RTMPSession.cpp @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #include @@ -44,7 +44,7 @@ namespace videocore #endif boost::char_separator sep("/"); boost::tokenizer > tokens(m_uri.path, sep ); - + auto itr = tokens.begin(); std::stringstream pp; { @@ -68,13 +68,13 @@ namespace videocore m_playPath = pp.str(); } long port = (m_uri.port > 0) ? m_uri.port : 1935; - + m_jobQueue.set_name("com.videocore.rtmp"); - + m_streamSession->connect(m_uri.host, static_cast(port), [&](IStreamSession& session, StreamStatus_t status) { streamStatusChanged(status); }); - + } RTMPSession::~RTMPSession() { @@ -86,25 +86,26 @@ namespace videocore void RTMPSession::setSessionParameters(videocore::IMetadata ¶meters) { - + RTMPSessionParameters_t& parms = dynamic_cast(parameters); m_bitrate = parms.getData(); m_frameDuration = parms.getData(); m_frameHeight = parms.getData(); m_frameWidth = parms.getData(); m_audioSampleRate = parms.getData(); + m_audioStereo = parms.getData(); } void RTMPSession::pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata) { - + std::shared_ptr buf = std::make_shared(size); buf->put(const_cast(data), size); - + const RTMPMetadata_t inMetadata = static_cast(metadata); - + m_jobQueue.enqueue([&,buf,inMetadata]() { - + std::vector chunk; std::vector & outb = this->m_outBuffer; size_t len = buf->size(); @@ -131,17 +132,17 @@ namespace videocore } m_previousChunkData[streamId] = ts; put_buff(chunk, p, tosend); - + outb.insert(outb.end(), chunk.begin(), chunk.end()); - - + + len -= tosend; p += tosend; - + while(len > 0) { tosend = std::min(len, m_currentChunkSize); p[-1] = RTMP_CHUNK_TYPE_3 | (streamId & 0x1F); - + outb.insert(outb.end(), p-1, p+tosend); p+=tosend; len-=tosend; @@ -149,23 +150,23 @@ namespace videocore this->write(&outb[0], outb.size()); outb.clear(); } - + } if(this->m_state != kClientStateConnected || outb.size() > 3072) { this->write(&outb[0], outb.size()); outb.clear(); } - + }); - + } void RTMPSession::sendPacket(uint8_t* data, size_t size, RTMPChunk_0 metadata) { RTMPMetadata_t md(0.); - + md.setData(metadata.timestamp.data, metadata.msg_length.data, metadata.msg_type_id, metadata.msg_stream_id); - + pushBuffer(data, size, md); } void @@ -179,17 +180,17 @@ namespace videocore count++; } if((m_streamSession->status() & kStreamStatusWriteBufferHasSpace) && m_streamOutRemainder.size()) { - + uint8_t* buffer; size_t size = m_streamOutRemainder.size(); - + m_streamOutRemainder.read(&buffer, size, false); // Read the entire buffer, but do not advance the read pointer. //printf("Remain %8zu\n ", size); size_t sent = m_streamSession->write(buffer, size); - + m_streamOutRemainder.read(&buffer, sent, true); // Advance the read pointer as far as we were able to send. } - + while((m_streamSession->status() & kStreamStatusWriteBufferHasSpace) && m_streamOutQueue.size() > 0) { //printf("StreamQueue: %zu\n", m_streamOutQueue.size()); std::shared_ptr front = m_streamOutQueue.front(); @@ -198,7 +199,7 @@ namespace videocore size_t size = front->size(); front->read(&buf, size); size_t sent = m_streamSession->write(buf, size); - + if(sent < size) { m_streamOutRemainder.put(buf+sent, size-sent); break; @@ -208,35 +209,35 @@ namespace videocore void RTMPSession::dataReceived() { - + static uint8_t buffer[4096] = {0}; bool stop1 = false; bool stop2 = false; do { - + size_t maxlen = m_streamInBuffer->total() - m_streamInBuffer->size(); size_t len = m_streamSession->read(buffer, maxlen); - + m_streamInBuffer->put(&buffer[0], len); - + while(m_streamInBuffer->size() > 0 && !stop1) { - + switch(m_state) { case kClientStateHandshake1s0: { uint8_t s0 ; m_streamInBuffer->get(&s0, 1); - + if(s0 == 0x03) { setClientState(kClientStateHandshake1s1); } } break; - + case kClientStateHandshake1s1: { if(m_streamInBuffer->size() >= kRTMPSignatureSize) { - + uint8_t* buf; size_t size = m_streamInBuffer->read(&buf, kRTMPSignatureSize); m_s1.resize(size); @@ -252,7 +253,7 @@ namespace videocore if(m_streamInBuffer->size() >= kRTMPSignatureSize) { uint8_t* buf; m_streamInBuffer->read(&buf, kRTMPSignatureSize); - + setClientState(kClientStateHandshakeComplete); handshake(); sendConnectPacket(); @@ -270,14 +271,14 @@ namespace videocore } } } - + } while((m_streamSession->status() & kStreamStatusReadBufferHasBytes) && !stop2); } void RTMPSession::setClientState(ClientState_t state) { printf("RTMPStatus: %d\n", state); - + m_state = state; m_callback(*this, state); } @@ -307,9 +308,9 @@ namespace videocore setClientState(kClientStateError); } } - + // RTMP - + void RTMPSession::handshake() { @@ -333,26 +334,26 @@ namespace videocore RTMPSession::handshake0() { char c0 = 0x03; - + setClientState(kClientStateHandshake0); - + write((uint8_t*)&c0, 1); - + handshake(); } void RTMPSession::handshake1() { setClientState(kClientStateHandshake1s0); - + m_c1.resize(kRTMPSignatureSize); uint8_t* p; m_c1.read(&p, kRTMPSignatureSize); uint64_t zero = 0; m_c1.put((uint8_t*)&zero, sizeof(uint64_t)); - + write(p, kRTMPSignatureSize); - + } void RTMPSession::handshake2() @@ -363,10 +364,10 @@ namespace videocore p += 4; uint32_t zero = 0; memcpy(p, &zero, sizeof(uint32_t)); - + write(m_s1(), m_s1.size()); } - + void RTMPSession::sendConnectPacket() { @@ -394,7 +395,7 @@ namespace videocore put_named_double(buff, "videoFunction", 1.); put_be16(buff, 0); put_byte(buff, kAMFObjectEnd); - + metadata.msg_length.data = static_cast( buff.size() ); sendPacket(&buff[0], buff.size(), metadata); } @@ -411,7 +412,7 @@ namespace videocore put_byte(buff, kAMFNull); put_string(buff, m_playPath); metadata.msg_length.data = static_cast (buff.size()); - + sendPacket(&buff[0], buff.size(), metadata); } void @@ -427,7 +428,7 @@ namespace videocore put_byte(buff, kAMFNull); put_string(buff, m_playPath); metadata.msg_length.data = static_cast( buff.size() ); - + sendPacket(&buff[0], buff.size(), metadata); } void @@ -443,7 +444,7 @@ namespace videocore put_double(buff, m_createStreamInvoke); put_byte(buff, kAMFNull); metadata.msg_length.data = static_cast( buff.size() ); - + sendPacket(&buff[0], buff.size(), metadata); } void @@ -454,7 +455,7 @@ namespace videocore metadata.msg_type_id = FLV_TAG_TYPE_INVOKE; std::vector buff; std::vector chunk; - + put_string(buff, "publish"); put_double(buff, ++m_numberOfInvokes); m_trackedCommands[m_numberOfInvokes] = "publish"; @@ -462,53 +463,53 @@ namespace videocore put_string(buff, m_playPath); put_string(buff, "live"); metadata.msg_length.data = static_cast( buff.size() ); - + sendPacket(&buff[0], buff.size(), metadata); } void RTMPSession::sendHeaderPacket() { std::vector outBuffer; - + std::vector enc; RTMPChunk_0 metadata = {{0}}; - + put_string(enc, "@setDataFrame"); put_string(enc, "onMetaData"); put_byte(enc, kAMFEMCAArray); put_be32(enc, 5+5+2); // videoEnabled + audioEnabled + 2 - + put_named_double(enc, "duration", 0.0); put_named_double(enc, "width", m_frameWidth); put_named_double(enc, "height", m_frameHeight); put_named_double(enc, "videodatarate", static_cast(m_bitrate) / 1024.); put_named_double(enc, "framerate", m_frameDuration); put_named_double(enc, "videocodecid", 7.); - - + + put_named_double(enc, "audiodatarate", 131152. / 1024.); put_named_double(enc, "audiosamplerate", m_audioSampleRate); put_named_double(enc, "audiosamplesize", 16); - put_named_bool(enc, "stereo", true); + put_named_bool(enc, "stereo", m_audioStereo); put_named_double(enc, "audiocodecid", 10.); - - + + put_named_double(enc, "filesize", 0.); put_be16(enc, 0); put_byte(enc, kAMFObjectEnd); size_t len = enc.size(); - - + + put_buff(outBuffer, (uint8_t*)&enc[0], static_cast(len)); - - + + metadata.msg_type_id = FLV_TAG_TYPE_META; metadata.msg_stream_id = kAudioChannelStreamId; metadata.msg_length.data = static_cast( outBuffer.size() ); metadata.timestamp.data = 0; - + sendPacket(&outBuffer[0], outBuffer.size(), metadata); - + } void RTMPSession::sendDeleteStream() @@ -522,11 +523,11 @@ namespace videocore m_trackedCommands[m_numberOfInvokes] = "deleteStream"; put_byte(buff, kAMFNull); put_double(buff, m_streamId); - + metadata.msg_length.data = static_cast( buff.size() ); - + sendPacket(&buff[0], buff.size(), metadata); - + } bool RTMPSession::parseCurrentData() @@ -534,9 +535,9 @@ namespace videocore uint8_t* p, *start ; m_streamInBuffer->read(&p, m_streamInBuffer->size(), false); start = p; - + if(!p) return false; - + int header_type = (p[0] & 0xC0) >> 6; p++; switch(header_type) { @@ -545,16 +546,16 @@ namespace videocore RTMPChunk_0 chunk; memcpy(&chunk, p, sizeof(RTMPChunk_0)); chunk.msg_length.data = get_be24((uint8_t*)&chunk.msg_length); - + p+=sizeof(chunk); - + switch(chunk.msg_type_id) { case RTMP_PT_BYTES_READ: { printf("received bytes read\n"); } break; - + case RTMP_PT_CHUNK_SIZE: { //unsigned long newChunkSize = get_be32(p); @@ -574,7 +575,7 @@ namespace videocore printf("received client bandwidth\n"); } break; - + case RTMP_PT_SERVER_BW: { printf("received server bandwidth\n"); @@ -591,32 +592,32 @@ namespace videocore printf("received video\n"); } break; - + case RTMP_PT_AUDIO: { printf("received audio\n"); } break; - + case RTMP_PT_METADATA: { printf("received metadata\n"); } break; - + case RTMP_PT_NOTIFY: { printf("received notify\n"); } break; - + default: { printf("received unknown packet type: 0x%02X\n", chunk.msg_type_id); } break; } - + p+=chunk.msg_length.data; } break; @@ -651,7 +652,7 @@ namespace videocore return true; } - + void RTMPSession::handleInvoke(uint8_t* p) { @@ -689,17 +690,17 @@ namespace videocore setClientState(kClientStateSessionStarted); } } - + } - + std::string RTMPSession::parseStatusCode(uint8_t *p) { uint8_t *start = p; std::map props; - + // skip over the packet id double num = get_double(p+1); // num p += sizeof(num) + 1; - + // keep reading until we find an AMF Object bool foundObject = false; while (!foundObject) { @@ -711,7 +712,7 @@ namespace videocore p += amfPrimitiveObjectSize(p); } } - + // read the properties of the object uint16_t nameLen, valLen; char propName[128], propVal[128]; @@ -734,11 +735,11 @@ namespace videocore props[propName] = ""; } } while (get_be24(p) != AMF_DATA_TYPE_OBJECT_END); - + p = start; return props["code"]; } - + int32_t RTMPSession::amfPrimitiveObjectSize(uint8_t* p) { switch(p[0]) { case AMF_DATA_TYPE_NUMBER: return 9; diff --git a/rtmp/RTMPSession.h b/rtmp/RTMPSession.h index 72650b99..473fb85e 100644 --- a/rtmp/RTMPSession.h +++ b/rtmp/RTMPSession.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #ifndef __videocore__RTMSession__ #define __videocore__RTMSession__ @@ -49,41 +49,42 @@ namespace videocore kRTMPSessionParameterHeight, kRTMPSessionParameterFrameDuration, kRTMPSessionParameterVideoBitrate, - kRTMPSessionParameterAudioFrequency + kRTMPSessionParameterAudioFrequency, + kRTMPSessionParameterStereo }; - typedef MetaData<'rtmp', int32_t, int32_t, double, int32_t, double> RTMPSessionParameters_t; + typedef MetaData<'rtmp', int32_t, int32_t, double, int32_t, double, bool> RTMPSessionParameters_t; enum { kRTMPMetadataTimestamp=0, kRTMPMetadataMsgLength, kRTMPMetadataMsgTypeId, kRTMPMetadataMsgStreamId }; - + typedef MetaData<'rtmp', int32_t, int32_t, uint8_t, int32_t> RTMPMetadata_t; typedef std::function RTMPSessionStateCallback_t; - + class RTMPSession : public IOutputSession { public: RTMPSession(std::string uri, RTMPSessionStateCallback_t callback); ~RTMPSession(); - + public: - + // Requires RTMPMetadata_t void pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata); - + void setSessionParameters(IMetadata& parameters); - + private: - + // Deprecate sendPacket void sendPacket(uint8_t* data, size_t size, RTMPChunk_0 metadata); - - - + + + void streamStatusChanged(StreamStatus_t status); void write(uint8_t* data, size_t size); void dataReceived(); @@ -92,42 +93,42 @@ namespace videocore void handshake0(); void handshake1(); void handshake2(); - + void sendConnectPacket(); void sendReleaseStream(); void sendFCPublish(); void sendCreateStream(); void sendPublish(); void sendHeaderPacket(); - + void sendDeleteStream(); - + bool parseCurrentData(); void handleInvoke(uint8_t* p); std::string parseStatusCode(uint8_t *p); int32_t amfPrimitiveObjectSize(uint8_t* p); private: - + JobQueue m_jobQueue; - + RingBuffer m_streamOutRemainder; Buffer m_s1, m_c1; - + std::queue > m_streamOutQueue; - + std::map m_previousChunkData; std::unique_ptr m_streamInBuffer; std::unique_ptr m_streamSession; std::vector m_outBuffer; http::url m_uri; - + RTMPSessionStateCallback_t m_callback; std::string m_playPath; std::string m_app; std::map m_trackedCommands; - - int64_t m_previousTimestamp; + + int64_t m_previousTimestamp; size_t m_currentChunkSize; int32_t m_streamId; int32_t m_createStreamInvoke; @@ -137,7 +138,8 @@ namespace videocore int32_t m_bitrate; double m_frameDuration; double m_audioSampleRate; - + bool m_audioStereo; + ClientState_t m_state; }; } diff --git a/sample/SampleBroadcaster/SampleBroadcaster.xcodeproj/project.pbxproj b/sample/SampleBroadcaster/SampleBroadcaster.xcodeproj/project.pbxproj index eac6c1df..e22749b3 100644 --- a/sample/SampleBroadcaster/SampleBroadcaster.xcodeproj/project.pbxproj +++ b/sample/SampleBroadcaster/SampleBroadcaster.xcodeproj/project.pbxproj @@ -214,7 +214,7 @@ ORGANIZATIONNAME = videocore; TargetAttributes = { 7265D5DB191994B80042DC3B = { - DevelopmentTeam = 486S5TJL4J; + DevelopmentTeam = G5RU2HL4SN; }; 7265D5FF191994B80042DC3B = { TestTargetID = 7265D5DB191994B80042DC3B; diff --git a/sources/iOS/MicSource.h b/sources/iOS/MicSource.h index 484be997..971b8af7 100644 --- a/sources/iOS/MicSource.h +++ b/sources/iOS/MicSource.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #ifndef __videocore__MicSource__ #define __videocore__MicSource__ @@ -41,7 +41,7 @@ namespace videocore { namespace iOS { class MicSource : public ISource, public std::enable_shared_from_this { public: - + /*! * Constructor. * @@ -51,37 +51,39 @@ namespace videocore { namespace iOS { * applications that may be capturing Audio Unit data and do not wish to capture this source. * */ - MicSource(double audioSampleRate = 44100., + MicSource(double sampleRate = 44100., + int channelCount = 2, std::function excludeAudioUnit = nullptr); - + /*! Destructor */ ~MicSource(); - - + + public: - + /*! ISource::setOutput */ void setOutput(std::shared_ptr output); - + /*! Used by the Audio Unit as a callback method */ void inputCallback(uint8_t* data, size_t data_size); - - /*! + + /*! * \return a reference to the source's Audio Unit */ const AudioUnit& audioUnit() const { return m_audioUnit; }; - + private: - + AudioComponentInstance m_audioUnit; AudioComponent m_component; - - double m_audioSampleRate; - + + double m_sampleRate; + int m_channelCount; + std::weak_ptr m_output; - + }; - + } } #endif /* defined(__videocore__MicSource__) */ diff --git a/sources/iOS/MicSource.mm b/sources/iOS/MicSource.mm index 4337b7b8..fd544a31 100644 --- a/sources/iOS/MicSource.mm +++ b/sources/iOS/MicSource.mm @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ of this software and associated documentation files (the "Software"), to deal LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #include "MicSource.h" #include @@ -37,23 +37,23 @@ static OSStatus handleInputBuffer(void *inRefCon, AudioBufferList *ioData) { videocore::iOS::MicSource* mc =static_cast(inRefCon); - + AudioBuffer buffer; buffer.mData = NULL; buffer.mDataByteSize = 0; buffer.mNumberChannels = 2; - + AudioBufferList buffers; buffers.mNumberBuffers = 1; buffers.mBuffers[0] = buffer; - + OSStatus status = AudioUnitRender(mc->audioUnit(), ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &buffers); - + if(!status) { mc->inputCallback((uint8_t*)buffers.mBuffers[0].mData, buffers.mBuffers[0].mDataByteSize); } @@ -62,50 +62,50 @@ static OSStatus handleInputBuffer(void *inRefCon, namespace videocore { namespace iOS { - - MicSource::MicSource(double audioSampleRate, std::function excludeAudioUnit) - : m_audioSampleRate(audioSampleRate) + + MicSource::MicSource(double sampleRate, int channelCount, std::function excludeAudioUnit) + : m_sampleRate(sampleRate), m_channelCount(channelCount) { - + AVAudioSession *session = [AVAudioSession sharedInstance]; - + [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker error:nil]; [session setActive:YES error:nil]; - + AudioComponentDescription acd; acd.componentType = kAudioUnitType_Output; acd.componentSubType = kAudioUnitSubType_VoiceProcessingIO; acd.componentManufacturer = kAudioUnitManufacturer_Apple; acd.componentFlags = 0; acd.componentFlagsMask = 0; - + m_component = AudioComponentFindNext(NULL, &acd); - + AudioComponentInstanceNew(m_component, &m_audioUnit); if(excludeAudioUnit) { excludeAudioUnit(m_audioUnit); } UInt32 flagOne = 1; - + AudioUnitSetProperty(m_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flagOne, sizeof(flagOne)); - + AudioStreamBasicDescription desc = {0}; - desc.mSampleRate = m_audioSampleRate; + desc.mSampleRate = m_sampleRate; desc.mFormatID = kAudioFormatLinearPCM; desc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - desc.mChannelsPerFrame = 2; + desc.mChannelsPerFrame = m_channelCount; desc.mFramesPerPacket = 1; desc.mBitsPerChannel = 16; desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame; desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket; - + AURenderCallbackStruct cb; cb.inputProcRefCon = this; cb.inputProc = handleInputBuffer; AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desc, sizeof(desc)); AudioUnitSetProperty(m_audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb)); - + AudioUnitInitialize(m_audioUnit); AudioOutputUnitStart(m_audioUnit); @@ -125,7 +125,7 @@ static OSStatus handleInputBuffer(void *inRefCon, auto output = m_output.lock(); if(output) { videocore::AudioBufferMetadata md (0.); - md.setData(m_audioSampleRate, 16, 2, false, shared_from_this()); + md.setData(m_sampleRate, 16, m_channelCount, false, shared_from_this()); output->pushBuffer(data, data_size, md); } } diff --git a/transforms/RTMP/AACPacketizer.cpp b/transforms/RTMP/AACPacketizer.cpp index 0fb8d70c..16abf93a 100644 --- a/transforms/RTMP/AACPacketizer.cpp +++ b/transforms/RTMP/AACPacketizer.cpp @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #include #include @@ -28,67 +28,73 @@ #include namespace videocore { namespace rtmp { - - AACPacketizer::AACPacketizer() - : m_audioTs(0), m_sentAudioConfig(false) + + AACPacketizer::AACPacketizer(float sampleRate, int channelCount) + : m_audioTs(0), m_sentAudioConfig(false), m_sampleRate(sampleRate), m_channelCount(channelCount) { memset(m_asc, 0, sizeof(m_asc)); } - + void AACPacketizer::pushBuffer(const uint8_t* const inBuffer, size_t inSize, IMetadata& metadata) { std::vector & outBuffer = m_outbuffer; - + outBuffer.clear(); - + + int flvStereoOrMono = (m_channelCount == 2 ? FLV_STEREO : FLV_MONO); + int flvSampleRate = FLV_SAMPLERATE_44100HZ; // default + if (m_sampleRate == 22050.0) { + flvSampleRate = FLV_SAMPLERATE_22050HZ; + } + int flags = 0; const int flags_size = 2; - - + + int ts = metadata.timestampDelta; - + auto output = m_output.lock(); - + RTMPMetadata_t outMeta(metadata.timestampDelta); - + if(inSize == 2 && !m_asc[0] && !m_asc[1]) { m_asc[0] = inBuffer[0]; m_asc[1] = inBuffer[1]; } - + if(output) { - - flags = FLV_CODECID_AAC | FLV_SAMPLERATE_44100HZ | FLV_SAMPLESSIZE_16BIT | FLV_STEREO; - + + flags = FLV_CODECID_AAC | flvSampleRate | FLV_SAMPLESSIZE_16BIT | flvStereoOrMono; + outBuffer.reserve(inSize + flags_size); - + put_byte(outBuffer, flags); put_byte(outBuffer, m_sentAudioConfig); if(!m_sentAudioConfig) { m_sentAudioConfig = true; put_buff(outBuffer, (uint8_t*)m_asc, sizeof(m_asc)); - + } else { - + put_buff(outBuffer, inBuffer, inSize); m_audioTs += metadata.timestampDelta; - + } outMeta.setData(ts, static_cast(outBuffer.size()), FLV_TAG_TYPE_AUDIO, kAudioChannelStreamId); - + output->pushBuffer(&outBuffer[0], outBuffer.size(), outMeta); } } - + void AACPacketizer::setOutput(std::shared_ptr output) { m_output = output; } - + } } diff --git a/transforms/RTMP/AACPacketizer.h b/transforms/RTMP/AACPacketizer.h index 676efd90..21a31577 100644 --- a/transforms/RTMP/AACPacketizer.h +++ b/transforms/RTMP/AACPacketizer.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #ifndef __videocore__AACPacketizer__ @@ -31,28 +31,30 @@ #include namespace videocore { namespace rtmp { - + class AACPacketizer : public ITransform { public: - AACPacketizer(); - + AACPacketizer(float sampleRate=44100, int channelCount=2); + void pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata); void setOutput(std::shared_ptr output); void setEpoch(const std::chrono::steady_clock::time_point epoch) { m_epoch = epoch; }; private: - - + + std::chrono::steady_clock::time_point m_epoch; std::weak_ptr m_output; std::vector m_outbuffer; - + double m_audioTs; char m_asc[2]; bool m_sentAudioConfig; + float m_sampleRate; + int m_channelCount; }; - + } } #endif /* defined(__videocore__AACPacketizer__) */ diff --git a/transforms/iOS/AACEncode.cpp b/transforms/iOS/AACEncode.cpp index a1cd3c15..bc43f813 100644 --- a/transforms/iOS/AACEncode.cpp +++ b/transforms/iOS/AACEncode.cpp @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,30 +20,52 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ #include #include namespace videocore { namespace iOS { - + struct UserData { uint8_t* data; int size; int packetSize; AudioStreamPacketDescription * pd ; - + } ; - + static const int kSamplesPerFrame = 1024; - - AACEncode::AACEncode( int frequencyInHz, int channelCount ) + + static char *FormatError(char *str, OSStatus error) + { + // see if it appears to be a 4-char-code + *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error); + if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { + str[0] = str[5] = '\''; + str[6] = '\0'; + } else + // no, format it as an integer + sprintf(str, "%d", (int)error); + return str; + } + + AACEncode::AACEncode(int frequencyInHz, int channelCount) : m_sentConfig(false) { + OSStatus result = 0; + char err[5]; + AudioStreamBasicDescription in = {0}, out = {0}; - + + + // passing anything except 48000, 44100, and 22050 for mSampleRate results in "!dat" + // OSStatus when querying for kAudioConverterPropertyMaximumOutputPacketSize property + // below in.mSampleRate = frequencyInHz; + // passing anything except 2 for mChannelsPerFrame results in "!dat" OSStatus when + // querying for kAudioConverterPropertyMaximumOutputPacketSize property below in.mChannelsPerFrame = channelCount; in.mBitsPerChannel = 16; in.mFormatFlags = kAudioFormatFlagsCanonical; @@ -51,18 +73,18 @@ namespace videocore { namespace iOS { in.mFramesPerPacket = 1; in.mBytesPerFrame = in.mBitsPerChannel * in.mChannelsPerFrame / 8; in.mBytesPerPacket = in.mFramesPerPacket*in.mBytesPerFrame; - + out.mFormatID = kAudioFormatMPEG4AAC; out.mFormatFlags = 0; out.mFramesPerPacket = kSamplesPerFrame; out.mSampleRate = frequencyInHz; out.mChannelsPerFrame = channelCount; - + bool canResume = true; UInt32 outputBitrate = 128000; // 128 kbps UInt32 propSize = sizeof(outputBitrate); UInt32 outputPacketSize = 0; - + AudioClassDescription requestedCodecs[1] = { { kAudioEncoderComponentType, @@ -70,22 +92,46 @@ namespace videocore { namespace iOS { kAppleSoftwareAudioCodecManufacturer } }; - - AudioConverterNewSpecific(&in, &out, 1, requestedCodecs, &m_audioConverter); - - AudioConverterSetProperty(m_audioConverter, kAudioConverterEncodeBitRate, propSize, &outputBitrate); - - AudioConverterSetProperty(m_audioConverter, kAudioConverterPropertyCanResumeFromInterruption, sizeof(canResume), &canResume); - - AudioConverterGetProperty(m_audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propSize, &outputPacketSize); - - + + result = AudioConverterNewSpecific(&in, &out, 1, requestedCodecs, &m_audioConverter); + if (result) FormatError(err, result); + + result = AudioConverterSetProperty(m_audioConverter, kAudioConverterEncodeBitRate, propSize, &outputBitrate); + if (result) FormatError(err, result); + + result = AudioConverterSetProperty(m_audioConverter, kAudioConverterPropertyCanResumeFromInterruption, sizeof(canResume), &canResume); + if (result) FormatError(err, result); + + result = AudioConverterGetProperty(m_audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propSize, &outputPacketSize); + if (result) FormatError(err, result); + + m_outputPacketMaxSize = outputPacketSize; - + m_bytesPerSample = 2 * channelCount; - - makeAsc((frequencyInHz == 44100) ? 4 : 3, channelCount); - + + int sampleRateIndex; + switch(frequencyInHz) { + case 48000: + sampleRateIndex = 3; + break; + case 44100: + sampleRateIndex = 4; + break; + case 22050: + sampleRateIndex = 7; + break; + case 11025: + sampleRateIndex = 10; + break; + case 8000: + sampleRateIndex = 11; + break; + default: + sampleRateIndex = 15; + } + makeAsc(sampleRateIndex, channelCount); + } AACEncode::~AACEncode() { AudioConverterDispose(m_audioConverter); @@ -101,15 +147,15 @@ namespace videocore { namespace iOS { AACEncode::ioProc(AudioConverterRef audioConverter, UInt32 *ioNumDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** ioPacketDesc, void* inUserData ) { UserData* ud = static_cast(inUserData); - + UInt32 maxPackets = ud->size / ud->packetSize; - + *ioNumDataPackets = std::min(maxPackets, *ioNumDataPackets); - + ioData->mBuffers[0].mData = ud->data; ioData->mBuffers[0].mDataByteSize = ud->size; ioData->mBuffers[0].mNumberChannels = 1; - + return noErr; } void @@ -118,7 +164,7 @@ namespace videocore { namespace iOS { const size_t sampleCount = size / m_bytesPerSample; const size_t aac_packet_count = sampleCount / kSamplesPerFrame; const size_t required_bytes = aac_packet_count * m_outputPacketMaxSize; - + if(m_outputBuffer.total() < (required_bytes)) { m_outputBuffer.resize(required_bytes); } @@ -132,29 +178,29 @@ namespace videocore { namespace iOS { l.mNumberBuffers=1; l.mBuffers[0].mDataByteSize = m_outputPacketMaxSize * num_packets; l.mBuffers[0].mData = p; - + std::unique_ptr ud(new UserData()); ud->size = static_cast(kSamplesPerFrame * m_bytesPerSample); ud->data = const_cast(p_out); ud->packetSize = static_cast(m_bytesPerSample); - + AudioStreamPacketDescription output_packet_desc[num_packets]; - + AudioConverterFillComplexBuffer(m_audioConverter, AACEncode::ioProc, ud.get(), &num_packets, &l, output_packet_desc); - + p += output_packet_desc[0].mDataByteSize; p_out += kSamplesPerFrame * m_bytesPerSample; } const size_t totalBytes = p - m_outputBuffer(); - + auto output = m_output.lock(); if(output && totalBytes) { if(!m_sentConfig) { output->pushBuffer((const uint8_t*)m_asc, sizeof(m_asc), metadata); m_sentConfig = true; } - + output->pushBuffer(m_outputBuffer(), totalBytes, metadata); } } diff --git a/transforms/iOS/AACEncode.h b/transforms/iOS/AACEncode.h index 8d20f090..5c437f4f 100644 --- a/transforms/iOS/AACEncode.h +++ b/transforms/iOS/AACEncode.h @@ -1,18 +1,18 @@ /* - + Video Core Copyright (c) 2014 James G. Hurley - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + */ /* @@ -39,32 +39,32 @@ #include namespace videocore { namespace iOS { - + class AACEncode : public ITransform { public: - AACEncode( int frequencyInHz, int channelCount ); + AACEncode(int frequencyInHz, int channelCount); ~AACEncode(); - + void setOutput(std::shared_ptr output) { m_output = output; }; void pushBuffer(const uint8_t* const data, size_t size, IMetadata& metadata); private: static OSStatus ioProc(AudioConverterRef audioConverter, UInt32 *ioNumDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** ioPacketDesc, void* inUserData ); void makeAsc(char sampleRateIndex, char channelCount); private: - + AudioConverterRef m_audioConverter; std::weak_ptr m_output; size_t m_bytesPerSample; uint32_t m_outputPacketMaxSize; - + Buffer m_outputBuffer; - + char m_asc[2]; bool m_sentConfig; - + }; - + } } #endif /* defined(__videocore__AACEncode__) */