11#import " XCWH264Encoder.h"
22
33#import < CoreMedia/CoreMedia.h>
4+ #import < ImageIO/ImageIO.h>
45#import < os/lock.h>
56#import < QuartzCore/QuartzCore.h>
67#import < VideoToolbox/VideoToolbox.h>
@@ -15,6 +16,7 @@ typedef NS_ENUM(NSUInteger, XCWVideoEncoderMode) {
1516 XCWVideoEncoderModeHEVCHardware,
1617 XCWVideoEncoderModeH264Hardware,
1718 XCWVideoEncoderModeH264Software,
19+ XCWVideoEncoderModeJPEG,
1820};
1921
2022static XCWVideoEncoderMode XCWVideoEncoderModeFromEnvironment (void ) {
@@ -25,6 +27,9 @@ static XCWVideoEncoderMode XCWVideoEncoderModeFromEnvironment(void) {
2527 if ([value isEqualToString: @" h264-software" ] || [value isEqualToString: @" software-h264" ]) {
2628 return XCWVideoEncoderModeH264Software;
2729 }
30+ if ([value isEqualToString: @" jpeg" ] || [value isEqualToString: @" jpg" ] || [value isEqualToString: @" mjpeg" ]) {
31+ return XCWVideoEncoderModeJPEG;
32+ }
2833 return XCWVideoEncoderModeHEVCHardware;
2934}
3035
@@ -33,6 +38,8 @@ static CMVideoCodecType XCWVideoCodecTypeForMode(XCWVideoEncoderMode mode) {
3338 case XCWVideoEncoderModeH264Hardware:
3439 case XCWVideoEncoderModeH264Software:
3540 return kCMVideoCodecType_H264 ;
41+ case XCWVideoEncoderModeJPEG:
42+ return kCMVideoCodecType_JPEG ;
3643 case XCWVideoEncoderModeHEVCHardware:
3744 default :
3845 return kCMVideoCodecType_HEVC ;
@@ -45,6 +52,8 @@ static CMVideoCodecType XCWVideoCodecTypeForMode(XCWVideoEncoderMode mode) {
4552 return @" h264" ;
4653 case XCWVideoEncoderModeH264Software:
4754 return @" h264-software" ;
55+ case XCWVideoEncoderModeJPEG:
56+ return @" jpeg" ;
4857 case XCWVideoEncoderModeHEVCHardware:
4958 default :
5059 return @" hevc" ;
@@ -57,6 +66,8 @@ static CMVideoCodecType XCWVideoCodecTypeForMode(XCWVideoEncoderMode mode) {
5766 return nil ;
5867 case XCWVideoEncoderModeH264Software:
5968 return @" com.apple.videotoolbox.videoencoder.h264" ;
69+ case XCWVideoEncoderModeJPEG:
70+ return nil ;
6071 case XCWVideoEncoderModeHEVCHardware:
6172 default :
6273 return nil ;
@@ -173,11 +184,106 @@ static uint32_t XCWReverseBits32(uint32_t value) {
173184 return @" hevc" ;
174185 case kCMVideoCodecType_H264 :
175186 return @" h264" ;
187+ case kCMVideoCodecType_JPEG :
188+ return @" jpeg" ;
176189 default :
177190 return [NSString stringWithFormat: @" 0x%08x " , (unsigned int )codecType];
178191 }
179192}
180193
194+ static CGFloat XCWJPEGQualityFromEnvironment (void ) {
195+ NSString *value = [[NSProcessInfo processInfo ] environment ][@" SIMDECK_JPEG_QUALITY" ];
196+ double quality = value.length > 0 ? value.doubleValue : 1.0 ;
197+ if (!isfinite (quality) || quality < 0.1 || quality > 1.0 ) {
198+ return 1.0 ;
199+ }
200+ return (CGFloat)quality;
201+ }
202+
203+ static CGColorSpaceRef XCWDeviceRGBColorSpace (void ) {
204+ static CGColorSpaceRef colorSpace = NULL ;
205+ static dispatch_once_t onceToken;
206+ dispatch_once (&onceToken, ^{
207+ colorSpace = CGColorSpaceCreateDeviceRGB ();
208+ });
209+ return colorSpace;
210+ }
211+
212+ static NSData *XCWJPEGDataFromPixelBuffer (CVPixelBufferRef pixelBuffer) {
213+ if (pixelBuffer == NULL ) {
214+ return nil ;
215+ }
216+
217+ CGImageRef image = NULL ;
218+ BOOL didLockPixelBuffer = NO ;
219+ OSType pixelFormat = CVPixelBufferGetPixelFormatType (pixelBuffer);
220+ if (pixelFormat == kCVPixelFormatType_32BGRA &&
221+ CVPixelBufferLockBaseAddress (pixelBuffer, kCVPixelBufferLock_ReadOnly ) == kCVReturnSuccess ) {
222+ didLockPixelBuffer = YES ;
223+ void *baseAddress = CVPixelBufferGetBaseAddress (pixelBuffer);
224+ size_t width = CVPixelBufferGetWidth (pixelBuffer);
225+ size_t height = CVPixelBufferGetHeight (pixelBuffer);
226+ size_t bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer);
227+ if (baseAddress != NULL && width > 0 && height > 0 && bytesPerRow >= width * 4 ) {
228+ CGDataProviderRef provider = CGDataProviderCreateWithData (NULL ,
229+ baseAddress,
230+ bytesPerRow * height,
231+ NULL );
232+ if (provider != NULL ) {
233+ image = CGImageCreate (width,
234+ height,
235+ 8 ,
236+ 32 ,
237+ bytesPerRow,
238+ XCWDeviceRGBColorSpace (),
239+ kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst ,
240+ provider,
241+ NULL ,
242+ false ,
243+ kCGRenderingIntentDefault );
244+ CGDataProviderRelease (provider);
245+ }
246+ }
247+ }
248+
249+ if (image == NULL ) {
250+ if (didLockPixelBuffer) {
251+ CVPixelBufferUnlockBaseAddress (pixelBuffer, kCVPixelBufferLock_ReadOnly );
252+ didLockPixelBuffer = NO ;
253+ }
254+ OSStatus imageStatus = VTCreateCGImageFromCVPixelBuffer (pixelBuffer, NULL , &image);
255+ if (imageStatus != noErr || image == NULL ) {
256+ return nil ;
257+ }
258+ }
259+
260+ NSMutableData *data = [NSMutableData data ];
261+ CGImageDestinationRef destination =
262+ CGImageDestinationCreateWithData ((__bridge CFMutableDataRef)data,
263+ CFSTR (" public.jpeg" ),
264+ 1 ,
265+ NULL );
266+ if (destination == NULL ) {
267+ CGImageRelease (image);
268+ if (didLockPixelBuffer) {
269+ CVPixelBufferUnlockBaseAddress (pixelBuffer, kCVPixelBufferLock_ReadOnly );
270+ }
271+ return nil ;
272+ }
273+
274+ NSDictionary *properties = @{
275+ (__bridge NSString *)kCGImageDestinationLossyCompressionQuality : @(XCWJPEGQualityFromEnvironment ()),
276+ };
277+ CGImageDestinationAddImage (destination, image, (__bridge CFDictionaryRef)properties);
278+ BOOL ok = CGImageDestinationFinalize (destination);
279+ CFRelease (destination);
280+ CGImageRelease (image);
281+ if (didLockPixelBuffer) {
282+ CVPixelBufferUnlockBaseAddress (pixelBuffer, kCVPixelBufferLock_ReadOnly );
283+ }
284+ return ok && data.length > 0 ? data : nil ;
285+ }
286+
181287static NSData *XCWCopySampleData (CMSampleBufferRef sampleBuffer) {
182288 CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer (sampleBuffer);
183289 if (blockBuffer == NULL ) {
@@ -505,6 +611,12 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
505611 return NO ;
506612 }
507613
614+ if (_encoderMode == XCWVideoEncoderModeJPEG) {
615+ return [self encodeJPEGPixelBufferLocked: pixelBuffer
616+ sourceWidth: sourceWidth
617+ sourceHeight: sourceHeight];
618+ }
619+
508620 if (![self ensureCompressionSessionWithWidth: targetWidth height: targetHeight]) {
509621 return NO ;
510622 }
@@ -553,6 +665,40 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
553665 return YES ;
554666}
555667
668+ - (BOOL )encodeJPEGPixelBufferLocked : (CVPixelBufferRef)pixelBuffer
669+ sourceWidth : (int32_t )sourceWidth
670+ sourceHeight : (int32_t )sourceHeight {
671+ uint64_t submittedAtUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
672+ if (_timestampOriginUs == 0 ) {
673+ _timestampOriginUs = submittedAtUs;
674+ }
675+ uint64_t relativeTimestampUs = submittedAtUs - _timestampOriginUs;
676+
677+ NSData *jpegData = XCWJPEGDataFromPixelBuffer (pixelBuffer);
678+ if (jpegData.length == 0 ) {
679+ _encodeFailureCount += 1 ;
680+ _lastEncodeStatus = -1 ;
681+ return NO ;
682+ }
683+
684+ _width = sourceWidth;
685+ _height = sourceHeight;
686+ _submittedFrameCount += 1 ;
687+ _outputFrameCount += 1 ;
688+ _keyFrameOutputCount += 1 ;
689+ _lastEncodeStatus = noErr;
690+ uint64_t nowUs = (uint64_t )(CACurrentMediaTime () * 1000000.0 );
691+ _latestEncodeLatencyUs = nowUs >= submittedAtUs ? nowUs - submittedAtUs : 0 ;
692+
693+ self.outputHandler (jpegData,
694+ relativeTimestampUs,
695+ YES ,
696+ @" jpeg" ,
697+ nil ,
698+ CGSizeMake (sourceWidth, sourceHeight));
699+ return YES ;
700+ }
701+
556702- (BOOL )ensureCompressionSessionWithWidth : (int32_t )width height : (int32_t )height {
557703 if (_compressionSession != NULL && _width == width && _height == height) {
558704 return YES ;
0 commit comments