几种播放音频文件的方式(十五) —— OpenAL框架之简单示例二(四)

版本记录

版本号 时间
V1.0 2017.12.29

前言

ios系统中有很多方式可以播放音频文件,这里我们就详细的说明下播放音乐文件的原理和实例。感兴趣的可以看我写的上面几篇。
1. 几种播放音频文件的方式(一) —— 播放本地音乐
2. 几种播放音频文件的方式(二) —— 音效播放
3. 几种播放音频文件的方式(三) —— 网络音乐播放
4. 几种播放音频文件的方式(四) —— 音频队列服务(Audio Queue Services)(一)
5. 几种播放音频文件的方式(五) —— 音频队列服务(Audio Queue Services)简介(二)
6. 几种播放音频文件的方式(六) —— 音频队列服务(Audio Queue Services)之关于音频队列(三)
7. 几种播放音频文件的方式(七) —— 音频队列服务(Audio Queue Services)之录制音频(四)
8. 几种播放音频文件的方式(八) —— 音频队列服务(Audio Queue Services)之播放音频(五)
9. 几种播放音频文件的方式(九) —— Media Player框架之基本概览(一)
10. 几种播放音频文件的方式(十) —— Media Player框架之简单播放音频示例(二)
11. 几种播放音频文件的方式(十一) —— AudioUnit框架之基本概览(一)
12. 几种播放音频文件的方式(十二) —— OpenAL框架之基本概览(一)
13. 几种播放音频文件的方式(十三) —— OpenAL框架之分步解析(二)
14. 几种播放音频文件的方式(十四) —— OpenAL框架之简单示例一(三)

GLAirplay示例

下面我们看一下示例。

演示AirPlay启用时如何独立使用第二台显示器。

下面我们看一下代码。

1. GLAirPlay/main.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The main file.
 */
 
#import <UIKit/UIKit.h>
 
#import "AppDelegate.h"
 
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
2.GLAirPlay/UserControlDelegate.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The object that conforms to this UserControlDelegate protocol is responsible for setting the GL cube's rotating radius.
 */
 
#import <Foundation/Foundation.h>
 
@protocol UserControlDelegate <NSObject>
 
-(float)rotatingRadius;
 
@end
3.GLAirPlay/GLViewController.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 This UIViewController configures the OpenGL ES view and its UI when an external display is connected/disconnected.
 */
 
#import <UIKit/UIKit.h>
#import "UserControlDelegate.h"
 
@interface GLViewController : UIViewController
 
@property (nonatomic, strong) UIViewController *userInterfaceOnTop;
@property (nonatomic, strong) UIViewController *userInterfaceFullscreen;
 
- (void)startAnimation;
- (void)stopAnimation;
- (void)screenDidConnect:(UIViewController *)userInterface;
- (void)screenDidDisconnect:(UIViewController *)userInterface;
- (void)setTargetScreen:(UIScreen *)targetScreen;
 
@end
4.GLAirPlay/GLView.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The OpenGL ES view which renders a rotating cube. Responsible for creating a CADisplayLink for the new target display when a connection/disconnection occurs.
 */
 
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import <QuartzCore/QuartzCore.h>
 
#import "GLView.h"
#import "CubePlayback.h"
 
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
 
GLfloat gCubeVertexData[216] =
{
    // Data layout for each line below is:
    // positionX, positionY, positionZ,     normalX, normalY, normalZ,
    0.5f, -0.5f, -0.5f,        1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,          1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    
    0.5f, 0.5f, -0.5f,         0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 1.0f, 0.0f,
    0.5f, 0.5f, 0.5f,          0.0f, 1.0f, 0.0f,
    0.5f, 0.5f, 0.5f,          0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 1.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 1.0f, 0.0f,
    
    -0.5f, 0.5f, -0.5f,        -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,       -1.0f, 0.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         -1.0f, 0.0f, 0.0f,
    -0.5f, 0.5f, 0.5f,         -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,       -1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        -1.0f, 0.0f, 0.0f,
    
    -0.5f, -0.5f, -0.5f,       0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, -0.5f,        0.0f, -1.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, -1.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, -0.5f,        0.0f, -1.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         0.0f, -1.0f, 0.0f,
    
    0.5f, 0.5f, 0.5f,          0.0f, 0.0f, 1.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    0.5f, -0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    0.5f, -0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    -0.5f, 0.5f, 0.5f,         0.0f, 0.0f, 1.0f,
    -0.5f, -0.5f, 0.5f,        0.0f, 0.0f, 1.0f,
    
    0.5f, -0.5f, -0.5f,        0.0f, 0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,       0.0f, 0.0f, -1.0f,
    0.5f, 0.5f, -0.5f,         0.0f, 0.0f, -1.0f,
    0.5f, 0.5f, -0.5f,         0.0f, 0.0f, -1.0f,
    -0.5f, -0.5f, -0.5f,       0.0f, 0.0f, -1.0f,
    -0.5f, 0.5f, -0.5f,        0.0f, 0.0f, -1.0f
};
 
static double GetTimeMS()
{
    return (CACurrentMediaTime()*1000.0);
}
 
 
@interface GLView ()
{
    NSInteger _animationFrameInterval;
    CADisplayLink *_displayLink;
    UIScreen *_targetScreen;
    
    EAGLContext *_context;
    
    // The pixel dimensions of the CAEAGLLayer
    GLint _backingWidth;
    GLint _backingHeight;
    
    // The OpenGL names for the framebuffer and renderbuffer used to render to this view
    GLuint _defaultFramebuffer, _colorRenderbuffer;
    
    // The OpenGL frame for the depth buffer
    GLuint _depthRenderbuffer;
    
    GLuint _vertexArray;
    GLuint _vertexBuffer;
    
    float _rotation;
    float _radius;
    
    double _renderTime;
    BOOL _zeroDeltaTime;
}
 
@property (nonatomic, strong) GLKBaseEffect *effect;
 
// OpenAL playback is wired up in the storyboard scene
@property (nonatomic, strong) IBOutlet CubePlayback *playback;
 
@end
 
 
@implementation GLView
 
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}
 
// The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder
{    
    if ((self = [super initWithCoder:coder]))
    {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        
        eaglLayer.opaque = TRUE;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        
        if (!_context || ![EAGLContext setCurrentContext:_context])
        {
            return nil;
        }
        
        [self setupGL];
        
        _animating = FALSE;
        _animationFrameInterval = 1;
        _displayLink = nil;
        
        _zeroDeltaTime = TRUE;
    }
    
    return self;
}
 
- (void)setupGL
{
    // Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer
    glGenFramebuffers(1, &_defaultFramebuffer);
    glGenRenderbuffers(1, &_colorRenderbuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _defaultFramebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
    
    // Create a depth buffer as we want to enalbe GL_DEPTH_TEST in this sample
    glGenRenderbuffers(1, &_depthRenderbuffer);
    
    // Create a GLKBaseEffect to render the object
    self.effect = [[GLKBaseEffect alloc] init];
    self.effect.light0.enabled = GL_TRUE;
    self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f);
    
    glEnable(GL_DEPTH_TEST);
    
    // Create a VAO that stores the cube vertex and normal data
    glGenVertexArraysOES(1, &_vertexArray);
    glBindVertexArrayOES(_vertexArray);
    
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData), gCubeVertexData, GL_STATIC_DRAW);
    
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
    glEnableVertexAttribArray(GLKVertexAttribNormal);
    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
    
    glBindVertexArrayOES(0);
}
 
- (void)drawView:(id)sender
{
    double currentTime = GetTimeMS();
    double deltaTime = _zeroDeltaTime ? 0.0 : currentTime - _renderTime;
    _renderTime = currentTime;
    
    if (_zeroDeltaTime)
        _zeroDeltaTime = FALSE;
    
    // Update animation states
    
    if (self.userControlDelegate)
        _radius = [self.userControlDelegate rotatingRadius];
    
    _rotation += deltaTime * 0.05 * M_PI / 180.0;
    
    // Update OpenGL
    
    [EAGLContext setCurrentContext:_context];
    
    glBindFramebuffer(GL_FRAMEBUFFER, _defaultFramebuffer);
    glViewport(0, 0, _backingWidth, _backingHeight);
    
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glBindVertexArrayOES(_vertexArray);
    
    // Compute the projection matrix
    float aspect = (GLfloat)_backingWidth / (GLfloat)_backingHeight;
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);
    
    self.effect.transform.projectionMatrix = projectionMatrix;
    
    // Compute the model view matrix
    GLKMatrix4 baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -4.0f);
    baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, _rotation, 0.0f, 1.0f, 0.0f);
    
    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -fabs(_radius));
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f);
    modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix);
    
    self.effect.transform.modelviewMatrix = modelViewMatrix;
    
    // Render the object with GLKit
    [self.effect prepareToDraw];
    
    glDrawArrays(GL_TRIANGLES, 0, 36);
    
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [_context presentRenderbuffer:GL_RENDERBUFFER];
    
    
    // Update OpenAL playback
    
    // cube's current position
    GLKVector4 pos = GLKVector4Make(0.0f, 0.0f, 0.0f, 1.0f);
    pos = GLKMatrix4MultiplyVector4(modelViewMatrix, pos);
    
    // source
    float *s = pos.v;
    [self.playback setSourcePos:s];
    
    // listener
    float l[] = { 1.0f, 0.0f, -4.0f };
    [self.playback setListenerPos:l];
    [self.playback setListenerRotation:(float)-M_PI_2]; //points inwards to the screen
}
 
- (BOOL)resizeFromLayer
{
    CAEAGLLayer *layer = (CAEAGLLayer*)self.layer;
    
    // Allocate color buffer backing based on the current layer size
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
    
    // Allocate storage for the depth buffer, and attach it to the framebuffer’s depth attachment point
    glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _backingWidth, _backingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderbuffer);
 
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return NO;
    }
        
    return YES;
}
 
- (void)layoutSubviews
{
    if ([self resizeFromLayer])
    {
        // An external display might just have been connected/disconnected. We do not want to
        // consider time spent in the connection/disconnection in the animation.
        _zeroDeltaTime = TRUE;
        [self drawView:nil];
    }
}
 
#pragma Display Link 
 
- (NSInteger)animationFrameInterval
{
    return _animationFrameInterval;
}
 
- (void)setAnimationFrameInterval:(NSInteger)frameInterval
{
    // Frame interval defines how many display frames must pass between each time the
    // display link fires. The display link will only fire 30 times a second when the
    // frame internal is two on a display that refreshes 60 times a second. The default
    // frame interval setting of one will fire 60 times a second when the display refreshes
    // at 60 times a second. A frame interval setting of less than one results in undefined
    // behavior.
    if (frameInterval >= 1)
    {
        _animationFrameInterval = frameInterval;
        
        if (_animating)
        {
            [self stopAnimation];
            [self startAnimation];
        }
    }
}
 
- (UIScreen *)targetScreen
{
    return _targetScreen;
}
 
- (void)setTargetScreen:(UIScreen *)screen
{
    if (_targetScreen != screen)
    {
        _targetScreen = screen;
    
        if (_animating)
        {
            [self stopAnimation];
            [self startAnimation];
        }
    }
}
 
- (void)startAnimation
{
    if (!_animating)
    {
        if (self.targetScreen) {
            // Create a CADisplayLink for the target display.
            // This will result in the native fps for whatever display you create it from.
            _displayLink = [self.targetScreen displayLinkWithTarget:self selector:@selector(drawView:)];
        }
        else {
            // Fall back to use CADislayLink's class method.
            // A CADisplayLink created using the class method is always bound to the internal display.
            _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView:)];
        }
        [_displayLink setFrameInterval:self.animationFrameInterval];
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        
        // Start playing sound if not already
        if (!self.playback.isPlaying)
            [self.playback startSound];
        
        // An external display might just have been connected/disconnected. We do not want to
        // consider time spent in the connection/disconnection in the animation.
        _zeroDeltaTime = TRUE;
        
        _animating = TRUE;
    }
}
 
- (void)stopAnimation
{
    if (_animating)
    {
        [_displayLink invalidate];
        _displayLink = nil;
        
        // Stop playing sound if currently playing
        if (self.playback.isPlaying)
            [self.playback stopSound];
        
        _animating = FALSE;
    }
}
 
- (void)dealloc
{
    // tear down OpenGL
    if (_defaultFramebuffer)
    {
        glDeleteFramebuffers(1, &_defaultFramebuffer);
        _defaultFramebuffer = 0;
    }
    
    if (_colorRenderbuffer)
    {
        glDeleteRenderbuffers(1, &_colorRenderbuffer);
        _colorRenderbuffer = 0;
    }
        
    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteVertexArraysOES(1, &_vertexArray);
    
    // tear down context
    if ([EAGLContext currentContext] == _context)
        [EAGLContext setCurrentContext:nil];
}
 
@end
5.GLAirPlay/CubePlayback.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 An Obj-C class which wraps an OpenAL playback environment
 */
 
#import <UIKit/UIKit.h>
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
 
@interface CubePlayback : NSObject
 
@property           BOOL isPlaying; // Whether the sound is playing or stopped
@property           BOOL wasInterrupted; // Whether playback was interrupted by the system
@property           float* sourcePos; // The coordinates of the sound source
@property           float* listenerPos; // The coordinates of the listener
@property           float listenerRotation; // The rotation angle of the listener in radians
 
- (void)initOpenAL;
- (void)teardownOpenAL;
 
- (void)startSound;
- (void)stopSound;
 
@end
6.GLAirPlay/MainViewController.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The root view controller. Demonstrates detailed steps on how to show content on an external display.
 */
 
#import <UIKit/UIKit.h>
 
@interface MainViewController : UIViewController
 
@property (nonatomic, strong) UIViewController *userInterfaceController;
@property (nonatomic, strong) UIViewController *glController;
 
@end
7.GLAirPlay/CubePlayback.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 An Obj-C class which wraps an OpenAL playback environment
 */
 
#import "CubePlayback.h"
#import "MyOpenALSupport.h"
 
@interface CubePlayback ()
{
    ALuint          _source;
    ALuint          _buffer;
    ALCcontext*     context;
    ALCdevice*      device;
    void            *_data;
    ALfloat         _sourceVolume;
    float           _sourcePos[3];
    float           _listenerPos[3];
    float           _listenerRotation;
}
@end
 
@implementation CubePlayback
 
#pragma mark Object Init / Maintenance
 
void interruptionListener(  void *  inClientData,
                          UInt32    inInterruptionState)
{
    CubePlayback *THIS = (__bridge CubePlayback*)inClientData;
    if (inInterruptionState == kAudioSessionBeginInterruption)
    {
        // do nothing
        [THIS teardownOpenAL];
        if ([THIS isPlaying]) {
            THIS->_wasInterrupted = YES;
            THIS->_isPlaying = NO;
        }
    }
    else if (inInterruptionState == kAudioSessionEndInterruption)
    {
        NSError *error;
        BOOL success = [[AVAudioSession sharedInstance] setActive:true error:&error];
        if (!success) {
            NSLog(@"Error setting audio session active! %@", error.localizedDescription);
        }
        [THIS initOpenAL];
        if (THIS->_wasInterrupted)
        {
            [THIS startSound];
            THIS->_wasInterrupted = NO;
        }
    }
}
 
// handle[Notification Name]Notification:   handleAVAudioSessionInterruptionNotification
#pragma mark AVAudioSession
- (void)handleAVAudioSessionInterruptionNotification:(NSNotification *)notification
{
    NSUInteger theInterruptionType = [[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] intValue];
    
    NSLog(@"Session interrupted > --- %s ---\n", theInterruptionType == AVAudioSessionInterruptionTypeBegan ? "Begin Interruption" : "End Interruption");
    
    if (theInterruptionType == AVAudioSessionInterruptionTypeBegan) {
        alcMakeContextCurrent(NULL);
        if (self.isPlaying) {
            self.wasInterrupted = YES;
        }
    } else if (theInterruptionType == AVAudioSessionInterruptionTypeEnded) {
        // make sure to activate the session
        NSError *error;
        bool success = [[AVAudioSession sharedInstance] setActive:YES error:&error];
        if (!success) NSLog(@"Error setting session active! %@\n", [error localizedDescription]);
        
        alcMakeContextCurrent(self.context);
        
        if (self.wasInterrupted)
        {
            [self startSound];
            self.wasInterrupted = NO;
        }
    }
}
 
- (void)initAVAudioSession
{
    // Configure the audio session
    AVAudioSession *sessionInstance = [AVAudioSession sharedInstance];
    NSError *error;
    
    // set the session category
    bool success = [sessionInstance setCategory:AVAudioSessionCategoryAmbient error:&error];
    if (!success) NSLog(@"Error setting AVAudioSession category! %@\n", [error localizedDescription]);
    
    double hwSampleRate = 44100.0;
    success = [sessionInstance setPreferredSampleRate:hwSampleRate error:&error];
    if (!success) NSLog(@"Error setting preferred sample rate! %@\n", [error localizedDescription]);
    
    // add interruption handler
    [[NSNotificationCenter defaultCenter]   addObserver:self
                                               selector:@selector(handleAVAudioSessionInterruptionNotification:)
                                                   name:AVAudioSessionInterruptionNotification
                                                 object:sessionInstance];
    
    // activate the audio session
    success = [sessionInstance setActive:YES error:&error];
    if (!success) NSLog(@"Error setting session active! %@\n", [error localizedDescription]);
}
 
- (id)init
{
    if (self = [super init]) {
        // initial position of the sound source and
        // initial position and rotation of the listener
        // will be set by the view
        
        // Setup AVAudioSession
        [self initAVAudioSession];
 
        _wasInterrupted = NO;
        
        // Initialize our OpenAL environment
        [self initOpenAL];
    }
    
    return self;
}
 
- (void)dealloc
{
    if (_data) free(_data);
    
    [self teardownOpenAL];
}
 
#pragma mark OpenAL
 
- (void) initBuffer
{
    ALenum      error = AL_NO_ERROR;
    ALenum      format = 0;
    ALsizei     size = 0;
    ALsizei     freq = 0;
    
    NSBundle*   bundle = [NSBundle mainBundle];
    
    // get some audio data from a wave file
    CFURLRef fileURL = CFBridgingRetain([NSURL fileURLWithPath:[bundle pathForResource:@"sound" ofType:@"wav"]]);
    
    if (fileURL)
    {
        _data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);
        CFRelease(fileURL);
        
        if((error = alGetError()) != AL_NO_ERROR) {
            printf("error loading sound: %x\n", error);
            exit(1);
        }
        
        // use the static buffer data API
        alBufferDataStaticProc(_buffer, format, _data, size, freq);
        
        if((error = alGetError()) != AL_NO_ERROR) {
            printf("error attaching audio to buffer: %x\n", error);
        }
    }
    else
    {
        printf("Could not find file!\n");
        _data = NULL;
    }
}
 
- (void) initSource
{
    ALenum error = AL_NO_ERROR;
    alGetError(); // Clear the error
    
    // Turn Looping ON
    alSourcei(_source, AL_LOOPING, AL_TRUE);
    
    // Set Source Position
    alSourcefv(_source, AL_POSITION, _sourcePos);
    
    // Set Source Reference Distance
    alSourcef(_source, AL_REFERENCE_DISTANCE, 0.15f);
    
    // attach OpenAL Buffer to OpenAL Source
    alSourcei(_source, AL_BUFFER, _buffer);
    
    if((error = alGetError()) != AL_NO_ERROR) {
        printf("Error attaching buffer to source: %x\n", error);
        exit(1);
    }
}
 
 
- (void)initOpenAL
{
    ALenum          error = AL_NO_ERROR;
    
    // Create a new OpenAL Device
    // Pass NULL to specify the system’s default output device
    device = alcOpenDevice(NULL);
    if (device != NULL)
    {
        // Create a new OpenAL Context
        // The new context will render to the OpenAL Device just created
        context = alcCreateContext(device, 0);
        if (context != NULL)
        {
            // Make the new context the Current OpenAL Context
            alcMakeContextCurrent(context);
            
            // Create some OpenAL Buffer Objects
            alGenBuffers(1, &_buffer);
            if((error = alGetError()) != AL_NO_ERROR) {
                printf("Error Generating Buffers: %x", error);
                exit(1);
            }
            
            // Create some OpenAL Source Objects
            alGenSources(1, &_source);
            if(alGetError() != AL_NO_ERROR)
            {
                printf("Error generating sources! %x\n", error);
                exit(1);
            }
            
        }
    }
    // clear any errors
    alGetError();
    
    [self initBuffer];
    [self initSource];
}
 
- (void)teardownOpenAL
{
    // Delete the Sources
    alDeleteSources(1, &_source);
    // Delete the Buffers
    alDeleteBuffers(1, &_buffer);
    
    //Release context
    alcDestroyContext(context);
    //Close device
    alcCloseDevice(device);
}
 
#pragma mark Play / Pause
 
- (void)startSound
{
    ALenum error;
    
    // Begin playing our source file
    alSourcePlay(_source);
    if((error = alGetError()) != AL_NO_ERROR) {
        printf("error starting source: %x\n", error);
    } else {
        // Mark our state as playing
        self.isPlaying = YES;
    }
}
 
- (void)stopSound
{
    ALenum error;
    
    // Stop playing our source file
    alSourceStop(_source);
    if((error = alGetError()) != AL_NO_ERROR) {
        printf("error stopping source: %x\n", error);
    } else {
        // Mark our state as not playing
        self.isPlaying = NO;
    }
}
 
#pragma mark Setters / Getters
 
- (ALCcontext *)context
{
    return context;
}
 
- (float*)sourcePos
{
    return _sourcePos;
}
 
- (void)setSourcePos:(float*)pos
{
    int i;
    for (i=0; i<3; i++)
        _sourcePos[i] = pos[i];
    
    // Move our audio source coordinates
    alSourcefv(_source, AL_POSITION, _sourcePos);
}
 
 
 
- (float*)listenerPos
{
    return _listenerPos;
}
 
- (void)setListenerPos:(float*)pos
{
    int i;
    for (i=0; i<3; i++)
        _listenerPos[i] = pos[i];
    
    // Move our listener coordinates
    alListenerfv(AL_POSITION, _listenerPos);
}
 
 
 
- (float)listenerRotation
{
    return _listenerRotation;
}
 
- (void)setListenerRotation:(float)radians
{
    _listenerRotation = radians;
    float ori[] = {cosf(radians), 0., sinf(radians), 0., 1., 0.}; //forward & up
    // Set our listener orientation (rotation)
    alListenerfv(AL_ORIENTATION, ori);
}
 
@end
8.GLAirPlay/MyOpenALSupport.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 OpenAL-related support functions
 */
 
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVAudioSession.h>
 
typedef ALvoid  AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
ALvoid  alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
{
    static  alBufferDataStaticProcPtr   proc = NULL;
    
    if (proc == NULL) {
        proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
    }
    
    if (proc)
        proc(bid, format, data, size, freq);
    
    return;
}
 
void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*    outSampleRate)
{
    OSStatus                        err = noErr;    
    UInt64                          fileDataSize = 0;
    AudioStreamBasicDescription     theFileFormat;
    UInt32                          thePropertySize = sizeof(theFileFormat);
    AudioFileID                     afid = 0;
    void*                           theData = NULL;
    
    // Open a file with ExtAudioFileOpen()
    err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, &afid);
    if(err) { printf("MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %d\n", (int)err); goto Exit; }
    
    // Get the audio data format
    err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat);
    if(err) { printf("MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %d\n", (int)err); goto Exit; }
    
    if (theFileFormat.mChannelsPerFrame > 2)  { 
        printf("MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit;
    }
    
    if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) { 
        printf("MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM\n"); goto Exit;
    }
    
    if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) { 
        printf("MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n"); goto Exit;
    }
    
    
    thePropertySize = sizeof(fileDataSize);
    err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
    if(err) { printf("MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %d\n", (int)err); goto Exit; }
    
    // Read all the data into memory
    UInt32      dataSize = (UInt32)fileDataSize;
    theData = malloc(dataSize);
    if (theData)
    {
        AudioFileReadBytes(afid, false, 0, &dataSize, theData);
        if(err == noErr)
        {
            // success
            *outDataSize = (ALsizei)dataSize;
            *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
            *outSampleRate = (ALsizei)theFileFormat.mSampleRate;
        }
        else 
        { 
            // failure
            free (theData);
            theData = NULL; // make sure to return NULL
            printf("MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %d\n", (int)err); goto Exit;
        }   
    }
    
Exit:
    // Dispose the ExtAudioFileRef, it is no longer needed
    if (afid) AudioFileClose(afid);
    return theData;
}
 
void TeardownOpenAL()
{
    ALCcontext  *context = NULL;
    ALCdevice   *device = NULL;
    ALuint      returnedName;
    
    // Delete the Sources
    alDeleteSources(1, &returnedName);
    // Delete the Buffers
    alDeleteBuffers(1, &returnedName);
    
    //Get active context
    context = alcGetCurrentContext();
    //Get device for active context
    device = alcGetContextsDevice(context);
    //Release context
    alcDestroyContext(context);
    //Close device
    alcCloseDevice(device);
}
9.GLAirPlay/UserInterfaceViewController.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 This UIViewController configures the appearances of the UI when an external display is connected/disconnected.
 */
 
#import "UserInterfaceViewController.h"
 
@interface UserInterfaceViewController () {
    CGRect _fsFrame;
}
@end
 
@implementation UserInterfaceViewController
 
- (float)rotatingRadius
{
    return self.slider.value * 2.0f;
}
 
- (void)screenDidConnect
{
    self.view.backgroundColor = [UIColor whiteColor];
    self.view.frame = _fsFrame; //fullscreen
    self.view.autoresizingMask =
        UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |
        UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin |
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
 
- (void)screenDidDisconnect
{
    self.view.backgroundColor = [UIColor clearColor];
    self.view.frame = CGRectMake(0, _fsFrame.size.height - 80, _fsFrame.size.width, 50);
    self.view.autoresizingMask =
        UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |
        UIViewAutoresizingFlexibleTopMargin | /*fixed bottom*/
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _fsFrame = self.view.frame;
}
 
- (BOOL)shouldAutorotate {
    return YES;
}
 
@end
10.GLAirPlay/GLView.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The OpenGL ES view which renders a rotating cube. Responsible for creating a CADisplayLink for the new target display when a connection/disconnection occurs.
 */
 
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
 
#import "UserControlDelegate.h"
 
@interface GLView : UIView
 
@property (readonly, nonatomic, getter=isAnimating) BOOL animating;
@property (nonatomic) NSInteger animationFrameInterval;
@property (nonatomic, strong) id <UserControlDelegate> userControlDelegate;
@property (nonatomic, weak) UIScreen *targetScreen;
 
- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView:(id)sender;
 
@end
11.GLAirPlay/AppDelegate.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The application's delegate.
 */
 
#import "AppDelegate.h"
#import "MainViewController.h"
#import "GLViewController.h"
 
@implementation AppDelegate
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) startAnimation];
    
    return YES;
}
                            
- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) stopAnimation];
}
 
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) stopAnimation];
}
 
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) startAnimation];
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) startAnimation];
}
 
- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    
    MainViewController *mainController = (MainViewController *)(self.window.rootViewController);
    [(GLViewController *)(mainController.glController) stopAnimation];
}
 
@end

12.GLAirPlay/GLViewController.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 This UIViewController configures the OpenGL ES view and its UI when an external display is connected/disconnected.
 */
 
#import "GLViewController.h"
#import "UserInterfaceViewController.h"
#import "GLView.h"
 
@implementation GLViewController
 
- (void)screenDidConnect:(UIViewController *)userInterface
{
    // Remove UI previously added atop the GL view
    for (UIView* v in [self.view subviews])
            [v removeFromSuperview];
    
    // One of these userInterface view controllers is visible at a time,
    // so release the other one to minimize memory usage
    self.userInterfaceOnTop = nil;
    self.userInterfaceFullscreen = userInterface;
        
    if (self.userInterfaceFullscreen)
    {
        // Setup UI (When an external display is connected, it will be added to mainViewController's view
        // in MainViewController/-screenDidChange:)
        [(UserInterfaceViewController *)self.userInterfaceFullscreen screenDidConnect];
        
        // Set the userControlDelegte, which is responsible for setting the cube's rotating radius
        [(GLView *)self.view setUserControlDelegate:(id)self.userInterfaceFullscreen];
    }
}
    
- (void)screenDidDisconnect:(UIViewController *)userInterface
{
    // One of these userInterface view controllers is visible at a time,
    // so release the other one to minimize memory usage
    self.userInterfaceFullscreen = nil;
    self.userInterfaceOnTop = userInterface;
    
    if (self.userInterfaceOnTop)
    {
        // Setup UI
        [(UserInterfaceViewController *)self.userInterfaceOnTop screenDidDisconnect];
        
        // Add UI on top
        [(GLView *)self.view addSubview:self.userInterfaceOnTop.view];
        
        // Set the userControlDelegte, which is responsible for setting the cube's rotating radius
        [(GLView *)self.view setUserControlDelegate:(id)self.userInterfaceOnTop];
    }
}
 
- (void)setTargetScreen:(UIScreen *)targetScreen
{
    // Delegate to the GL view to create a CADisplayLink for the target display (UIScreen/-displayLinkWithTarget:selector:)
    // This will result in the native fps for whatever display you create it from.
    [(GLView *)self.view setTargetScreen:targetScreen];
}
 
- (void)startAnimation
{
    [(GLView *)self.view startAnimation];
}
 
- (void)stopAnimation
{
    [(GLView *)self.view stopAnimation];
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.autoresizingMask =
        UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |
        UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin |
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
- (BOOL)shouldAutorotate {
    return YES;
}
 
@end
13.GLAirPlay/UserInterfaceViewController.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 This UIViewController configures the appearances of the UI when an external display is connected/disconnected.
 */
 
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
 
#import "UserControlDelegate.h"
 
@interface UserInterfaceViewController : UIViewController <UserControlDelegate>
 
@property (nonatomic, weak) IBOutlet UISlider *slider;
 
- (void)screenDidConnect;
- (void)screenDidDisconnect;
 
@end
14.GLAirPlay/AppDelegate.h
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The application's delegate.
 */
 
#import <UIKit/UIKit.h>
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (nonatomic, strong) UIWindow *window;
 
@end
15.GLAirPlay/MainViewController.m
/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 The root view controller. Demonstrates detailed steps on how to show content on an external display.
 */
 
#import "MainViewController.h"
#import "GLViewController.h"
 
@interface MainViewController ()
 
@property (nonatomic, strong) UIWindow *extWindow;
@property (nonatomic, strong) UIScreen *extScreen;
 
@end
 
@implementation MainViewController
 
- (void)screenDidChange:(NSNotification *)notification
{
    // To display content on an external display, do the following:
    // 1. Use the screens class method of the UIScreen class to determine if an external display is available.
    NSArray *screens = [UIScreen screens];
    
    NSUInteger screenCount = [screens count];
    
    if (screenCount > 1)
    {
        // 2. If an external screen is available, get the screen object and look at the values in its availableModes
        // property. This property contains the configurations supported by the screen.
        
        // Select first external screen
        self.extScreen = [screens objectAtIndex:1]; //index 0 is your iPhone/iPad
        NSArray *availableModes = [self.extScreen availableModes];
        
        // 3. Select the UIScreenMode object corresponding to the desired resolution and assign it to the currentMode
        // property of the screen object.
        
        // Select the highest resolution in this sample
        NSInteger selectedRow = [availableModes count] - 1;
        self.extScreen.currentMode = [availableModes objectAtIndex:selectedRow];
        
        // Set a proper overscanCompensation mode
        self.extScreen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
        
        if (self.extWindow == nil) {
            // 4. Create a new window object (UIWindow) to display your content.
            UIWindow *extWindow = [[UIWindow alloc] initWithFrame:[self.extScreen bounds]];
            self.extWindow = extWindow;
            self.extWindow.rootViewController = self.glController;
        }
        
        // 5. Assign the screen object to the screen property of your new window.
        self.extWindow.screen = self.extScreen;
        
        // 6. Configure the window (by adding views or setting up your OpenGL ES rendering context).
        
        // Resize the GL view to fit the external screen
        self.glController.view.frame = self.extWindow.frame;
        
        // Set the target screen to the external screen
        // This will let the GL view create a CADisplayLink that fires at the native fps of the target display.
        [(GLViewController *)self.glController setTargetScreen:self.extScreen];
        
        // Configure user interface
        // In this sample, we use the same UI layout when an external display is connected or not.
        // In your real application, you probably want to provide distinct UI layouts for best user experience.
        [(GLViewController *)self.glController screenDidConnect:self.userInterfaceController];
        
        // Add the GL view
        [self.extWindow addSubview:self.glController.view];
            
        // 7. Show the window.
        [self.extWindow makeKeyAndVisible];
        
        // On the iPhone/iPad screen
        // Remove the GL view (it is displayed on the external screen)
        for (UIView* v in [self.view subviews])
            [v removeFromSuperview];
        
        // Display the fullscreen UI on the iPhone/iPad screen
        [self.view addSubview:self.userInterfaceController.view];
    }
    else //handles disconnection of the external display
    {
        // Release external screen and window
        self.extScreen = nil;
        self.extWindow = nil;
        
        // On the iPhone/iPad screen
        // Remove the fullscreen UI (a window version will be displayed atop the GL view)
        for (UIView* v in [self.view subviews])
            [v removeFromSuperview];
        
        // Resize the GL view to fit the iPhone/iPad screen
        self.glController.view.frame = self.view.frame;
        
        // Set the target screen to the main screen
        // This will let the GL view create a CADisplayLink that fires at the native fps of the target display.
        [(GLViewController *)self.glController setTargetScreen:[UIScreen mainScreen]];
        
        // Configure user interface
        // In this sample, we use the same UI layout when an external display is connected or not.
        // In your real application, you probably want to provide distinct UI layouts for best user experience.
        [(GLViewController *)self.glController screenDidDisconnect:self.userInterfaceController];
        
        // Display the GL view on the iPhone/iPad screen
        [self.view addSubview:self.glController.view];
    }
}
 
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIScreenDidConnectNotification
                                                  object:nil];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIScreenDidDisconnectNotification
                                                  object:nil];
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIViewController *userInterfaceController = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"UserInterfaceViewController"];
    self.userInterfaceController = userInterfaceController;
    
    UIViewController *glController = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"GLViewController"];
    self.glController = glController;
    
    // No notifications are sent for screens that are present when the app is launched.
    [self screenDidChange:nil];
    
    // Register for screen connect and disconnect notifications.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(screenDidChange:)
                                                 name:UIScreenDidConnectNotification
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(screenDidChange:)
                                                 name:UIScreenDidDisconnectNotification
                                               object:nil];
}
 
- (BOOL)shouldAutorotate {
    return YES;
}
 
@end

后记

未完,待续~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容