mirror of
https://github.com/libretro/gambatte-libretro.git
synced 2025-02-11 11:25:16 +00:00
- Add CoreAudio engine with rate estimation and buffer status support. Default engine on Mac OS X.
- 44100 Hz default sample rate on OS X, since OS X tends to resample everything to 44100 Hz. - Get rid of buffer status averaging in OpenAlEngine, since it makes assumptions on usage pattern that shouldn't be made. - Extend ringbuffer.h to support resetting size, and move it to common dir since gambatte_qt/coreaudioengine uses it too now. git-svn-id: https://gambatte.svn.sourceforge.net/svnroot/gambatte@185 9dfb2916-2d38-0410-aef4-c5fe6c9ffc24
This commit is contained in:
parent
2638002fd3
commit
8a321f16c5
@ -1,5 +1,5 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aam<EFBFBD>s *
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
@ -19,20 +19,20 @@
|
||||
#ifndef RINGBUFFER_H
|
||||
#define RINGBUFFER_H
|
||||
|
||||
#include "array.h"
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
template<typename T>
|
||||
class RingBuffer {
|
||||
T *const buf;
|
||||
const std::size_t sz;
|
||||
Array<T> buf;
|
||||
std::size_t sz;
|
||||
std::size_t rpos;
|
||||
std::size_t wpos;
|
||||
|
||||
public:
|
||||
RingBuffer(const std::size_t sz_in) : buf(new T[sz_in + 1]), sz(sz_in + 1), rpos(0), wpos(0) {}
|
||||
~RingBuffer() { delete []buf; }
|
||||
RingBuffer(const std::size_t sz_in = 0) : sz(0), rpos(0), wpos(0) { reset(sz_in); }
|
||||
|
||||
std::size_t avail() const {
|
||||
return (wpos < rpos ? 0 : sz) + rpos - wpos - 1;
|
||||
@ -46,6 +46,8 @@ public:
|
||||
|
||||
void read(T *out, std::size_t num);
|
||||
|
||||
void reset(std::size_t sz_in);
|
||||
|
||||
std::size_t size() const {
|
||||
return sz - 1;
|
||||
}
|
||||
@ -59,7 +61,7 @@ public:
|
||||
|
||||
template<typename T>
|
||||
void RingBuffer<T>::fill(const T value) {
|
||||
std::fill(buf, buf + sz, value);
|
||||
std::fill(buf + 0, buf + sz, value);
|
||||
rpos = 0;
|
||||
wpos = sz - 1;
|
||||
}
|
||||
@ -82,6 +84,13 @@ void RingBuffer<T>::read(T *out, std::size_t num) {
|
||||
rpos = 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RingBuffer<T>::reset(const std::size_t sz_in) {
|
||||
sz = sz_in + 1;
|
||||
rpos = wpos = 0;
|
||||
buf.reset(sz_in ? sz : 0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RingBuffer<T>::write(const T *in, std::size_t num) {
|
||||
if (wpos + num > sz) {
|
@ -1,5 +1,5 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* Copyright (C) 2007 by Sindre AamÂs *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
@ -19,7 +19,9 @@
|
||||
#include "addaudioengines.h"
|
||||
|
||||
#include "audioengines/openalengine.h"
|
||||
#include "audioengines/coreaudioengine.h"
|
||||
|
||||
void addAudioEngines(std::vector<AudioEngine*> &audioEngines, WId /*winId*/) {
|
||||
audioEngines.push_back(new CoreAudioEngine);
|
||||
audioEngines.push_back(new OpenAlEngine);
|
||||
}
|
||||
|
285
gambatte_qt/src/framework/audioengines/coreaudioengine.cpp
Normal file
285
gambatte_qt/src/framework/audioengines/coreaudioengine.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "coreaudioengine.h"
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
static int createMutex(pthread_mutex_t* &mutptr) {
|
||||
mutptr = new pthread_mutex_t;
|
||||
const int ret = pthread_mutex_init(mutptr, NULL);
|
||||
|
||||
if (ret) {
|
||||
delete mutptr;
|
||||
mutptr = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void destroyMutex(pthread_mutex_t* &mutptr) {
|
||||
if (mutptr) {
|
||||
pthread_mutex_lock(mutptr);
|
||||
pthread_mutex_destroy(mutptr);
|
||||
delete mutptr;
|
||||
mutptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int createCond(pthread_cond_t* &condptr) {
|
||||
condptr = new pthread_cond_t;
|
||||
const int ret = pthread_cond_init(condptr, NULL);
|
||||
|
||||
if (ret) {
|
||||
delete condptr;
|
||||
condptr = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void destroyCond(pthread_cond_t* &condptr) {
|
||||
if (condptr) {
|
||||
pthread_cond_destroy(condptr);
|
||||
delete condptr;
|
||||
condptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
class MutexLocker {
|
||||
pthread_mutex_t *const mut;
|
||||
|
||||
public:
|
||||
const int err;
|
||||
|
||||
MutexLocker(pthread_mutex_t *mut) : mut(mut), err(pthread_mutex_lock(mut)) {
|
||||
}
|
||||
|
||||
~MutexLocker() {
|
||||
if (!err)
|
||||
pthread_mutex_unlock(mut);
|
||||
}
|
||||
};
|
||||
|
||||
CoreAudioEngine::CoreAudioEngine() : AudioEngine("CoreAudio"), outUnit(0), outUnitState(UNIT_CLOSED), mutex(NULL), availCond(NULL), rateEst(0), rateVar(0), running(false) {
|
||||
}
|
||||
|
||||
CoreAudioEngine::~CoreAudioEngine() {
|
||||
uninit();
|
||||
}
|
||||
|
||||
unsigned CoreAudioEngine::read(void *const stream, unsigned frames, const Float64 rateScalar) {
|
||||
MutexLocker mutlock(mutex);
|
||||
|
||||
if (!mutlock.err) {
|
||||
{
|
||||
const Float64 rateEst = rate() / rateScalar;
|
||||
rateVar = (rateVar * 15 + std::fabs(rateEst - this->rateEst)) * (1.0 / 16.0);
|
||||
this->rateEst = rateEst;
|
||||
}
|
||||
|
||||
if (frames > rbuf.used() / 2)
|
||||
frames = rbuf.used() / 2;
|
||||
|
||||
rbuf.read(reinterpret_cast<SInt16*>(stream), frames * 2);
|
||||
|
||||
pthread_cond_signal(availCond);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
ComponentResult CoreAudioEngine::renderProc(void *inRefCon, AudioUnitRenderActionFlags */*inActionFlags*/, const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 /*inBusNumber*/, UInt32 inNumFrames, AudioBufferList *ioData)
|
||||
{
|
||||
ioData->mBuffers[0].mDataByteSize = reinterpret_cast<CoreAudioEngine*>(inRefCon)->read(ioData->mBuffers[0].mData, inNumFrames, inTimeStamp->mRateScalar) * 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CoreAudioEngine::doInit(const int rate, const unsigned latency) {
|
||||
{
|
||||
Component comp;
|
||||
|
||||
{
|
||||
ComponentDescription desc;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
if ((comp = FindNextComponent(NULL, &desc)) == NULL) {
|
||||
std::fprintf(stderr, "Failed to find output unit component\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComponentResult err = OpenAComponent(comp, &outUnit)) {
|
||||
std::fprintf(stderr, "Failed to open output unit component: %d\n", static_cast<int>(err));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
outUnitState = UNIT_OPENED;
|
||||
}
|
||||
|
||||
if (ComponentResult err = AudioUnitInitialize(outUnit)) {
|
||||
std::fprintf(stderr, "Failed to initialize output unit component: %d\n", static_cast<int>(err));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
outUnitState = UNIT_INITED;
|
||||
|
||||
{
|
||||
AudioStreamBasicDescription desc;
|
||||
|
||||
desc.mSampleRate=rate;
|
||||
desc.mFormatID=kAudioFormatLinearPCM;
|
||||
desc.mChannelsPerFrame=2;
|
||||
desc.mBitsPerChannel=16;
|
||||
desc.mFramesPerPacket = 1;
|
||||
desc.mBytesPerPacket = desc.mBytesPerFrame = 4;
|
||||
desc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
desc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
#endif
|
||||
|
||||
if (ComponentResult err = AudioUnitSetProperty(outUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desc, sizeof(AudioStreamBasicDescription))) {
|
||||
std::fprintf(stderr, "Failed to set the input format: %d\n", static_cast<int>(err));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AURenderCallbackStruct renderCallback;
|
||||
renderCallback.inputProc = renderProc;
|
||||
renderCallback.inputProcRefCon = this;
|
||||
|
||||
if (ComponentResult err = AudioUnitSetProperty(outUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct))) {
|
||||
std::fprintf(stderr, "Failed to set render callback: %d\n", static_cast<int>(err));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (int err = createMutex(mutex)) {
|
||||
std::fprintf(stderr, "Failed to create mutex: %d\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (int err = createCond(availCond)) {
|
||||
std::fprintf(stderr, "Failed to create condition variable: %d\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rbuf.reset(((rate * latency + 500) / 1000) * 2);
|
||||
rbuf.fill(0);
|
||||
rateEst = rate;
|
||||
rateVar = rate >> 12;
|
||||
|
||||
return rate;
|
||||
|
||||
fail:
|
||||
uninit();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CoreAudioEngine::uninit() {
|
||||
if (outUnitState >= UNIT_INITED)
|
||||
AudioUnitUninitialize(outUnit);
|
||||
|
||||
if (outUnitState >= UNIT_OPENED)
|
||||
CloseComponent(outUnit);
|
||||
|
||||
destroyMutex(mutex);
|
||||
destroyCond(availCond);
|
||||
outUnitState = UNIT_CLOSED;
|
||||
running = false;
|
||||
rbuf.reset(0);
|
||||
}
|
||||
|
||||
void CoreAudioEngine::pause() {
|
||||
if (running) {
|
||||
AudioOutputUnitStop(outUnit);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
int CoreAudioEngine::doWrite(void *const buffer, unsigned samples) {
|
||||
if (!running) {
|
||||
if (ComponentResult err = AudioOutputUnitStart(outUnit)) {
|
||||
std::fprintf(stderr, "Failed to start output unit: %d\n", static_cast<int>(err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
running = true;
|
||||
}
|
||||
|
||||
SInt16 *inBuf = reinterpret_cast<SInt16*>(buffer);
|
||||
|
||||
{
|
||||
std::size_t avail;
|
||||
|
||||
while ((avail = rbuf.avail() / 2) < samples) {
|
||||
rbuf.write(inBuf, avail * 2);
|
||||
inBuf += avail * 2;
|
||||
samples -= avail;
|
||||
pthread_cond_wait(availCond, mutex);
|
||||
}
|
||||
}
|
||||
|
||||
rbuf.write(inBuf, samples * 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CoreAudioEngine::write(void *const buffer, const unsigned samples, BufferState &preBufState_out, RateEst::Result &rate_out) {
|
||||
MutexLocker mutlock(mutex);
|
||||
|
||||
if (mutlock.err)
|
||||
return -1;
|
||||
|
||||
preBufState_out.fromUnderrun = rbuf.used();
|
||||
preBufState_out.fromOverflow = rbuf.avail();
|
||||
rate_out.est = rateEst + 0.5;
|
||||
rate_out.var = rateVar + 0.5;
|
||||
|
||||
return doWrite(buffer, samples);
|
||||
}
|
||||
|
||||
int CoreAudioEngine::write(void *const buffer, const unsigned samples) {
|
||||
MutexLocker mutlock(mutex);
|
||||
|
||||
if (mutlock.err)
|
||||
return -1;
|
||||
|
||||
return doWrite(buffer, samples);
|
||||
}
|
||||
|
||||
const AudioEngine::BufferState CoreAudioEngine::bufferState() const {
|
||||
MutexLocker mutlock(mutex);
|
||||
const BufferState bstate = { fromUnderrun: rbuf.used(), fromOverflow: rbuf.avail() };
|
||||
return bstate;
|
||||
}
|
||||
|
||||
const RateEst::Result CoreAudioEngine::rateEstimate() const {
|
||||
MutexLocker mutlock(mutex);
|
||||
const RateEst::Result r = { est: rateEst + 0.5, var: rateVar + 0.5 };
|
||||
return r;
|
||||
}
|
56
gambatte_qt/src/framework/audioengines/coreaudioengine.h
Normal file
56
gambatte_qt/src/framework/audioengines/coreaudioengine.h
Normal file
@ -0,0 +1,56 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef CORE_AUDIO_ENGINE_H
|
||||
#define CORE_AUDIO_ENGINE_H
|
||||
|
||||
#include "../audioengine.h"
|
||||
#include <ringbuffer.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <pthread.h>
|
||||
|
||||
class CoreAudioEngine : public AudioEngine {
|
||||
enum { UNIT_CLOSED = 0, UNIT_OPENED, UNIT_INITED };
|
||||
|
||||
RingBuffer<SInt16> rbuf;
|
||||
AudioUnit outUnit;
|
||||
int outUnitState;
|
||||
pthread_mutex_t *mutex;
|
||||
pthread_cond_t *availCond;
|
||||
Float64 rateEst;
|
||||
Float64 rateVar;
|
||||
bool running;
|
||||
|
||||
static ComponentResult renderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData);
|
||||
unsigned read(void *stream, unsigned frames, Float64 rateScalar);
|
||||
int doInit(int rate, unsigned latency);
|
||||
int doWrite(void *buffer, unsigned frames);
|
||||
|
||||
public:
|
||||
CoreAudioEngine();
|
||||
~CoreAudioEngine();
|
||||
void uninit();
|
||||
int write(void *buffer, unsigned frames);
|
||||
int write(void *buffer, unsigned samples, BufferState &preBufState_out, RateEst::Result &rate_out);
|
||||
const BufferState bufferState() const;
|
||||
const RateEst::Result rateEstimate() const;
|
||||
void pause();
|
||||
};
|
||||
|
||||
#endif
|
@ -116,9 +116,7 @@ device(NULL),
|
||||
context(NULL),
|
||||
source(0),
|
||||
buffers(0),
|
||||
bufPos(0),
|
||||
fromUnderrun(0),
|
||||
rate(0) {
|
||||
bufPos(0) {
|
||||
}
|
||||
|
||||
OpenAlEngine::~OpenAlEngine() {
|
||||
@ -179,11 +177,9 @@ int OpenAlEngine::doInit(const int rate, unsigned latency) {
|
||||
}
|
||||
|
||||
failureCleaner.failed = false;
|
||||
this->rate = rate;
|
||||
buffers = rate * latency / (BUF_SZ * 1000);
|
||||
++buffers;
|
||||
// std::printf("buffers: %u\n", buffers);
|
||||
fromUnderrun = 0;
|
||||
buf = new qint16[BUF_SZ * 2];
|
||||
bufPos = 0;
|
||||
|
||||
@ -251,7 +247,7 @@ int OpenAlEngine::write(void *const data, const unsigned samples) {
|
||||
{
|
||||
ALuint bufid = 0;
|
||||
alGenBuffers(1, &bufid);
|
||||
alBufferData(bufid, AL_FORMAT_STEREO16, buf, BUF_SZ * sizeof(qint16) * 2, rate);
|
||||
alBufferData(bufid, AL_FORMAT_STEREO16, buf, BUF_SZ * sizeof(qint16) * 2, rate());
|
||||
alSourceQueueBuffers(source, 1, &bufid);
|
||||
}
|
||||
}
|
||||
@ -275,16 +271,16 @@ int OpenAlEngine::write(void *const data, const unsigned samples) {
|
||||
const AudioEngine::BufferState OpenAlEngine::bufferState() const {
|
||||
deleteProcessedBufs();
|
||||
|
||||
ALint queued = getSourcei(source, AL_BUFFERS_QUEUED);
|
||||
// queued -= 1;
|
||||
queued *= BUF_SZ;
|
||||
queued += bufPos;
|
||||
queued -= BUF_SZ >> 1;
|
||||
unsigned fromUnderrun = 0;
|
||||
|
||||
if (queued < 0)
|
||||
queued = 0;
|
||||
{
|
||||
const ALint queued = getSourcei(source, AL_BUFFERS_QUEUED);
|
||||
|
||||
fromUnderrun = fromUnderrun + queued >> 1;
|
||||
if (queued > 0)
|
||||
fromUnderrun = queued * BUF_SZ - (BUF_SZ >> 1);
|
||||
}
|
||||
|
||||
fromUnderrun += bufPos;
|
||||
|
||||
const BufferState s = { fromUnderrun: fromUnderrun, fromOverflow: fromUnderrun > (buffers + 1) * BUF_SZ ? 0 : (buffers + 1) * BUF_SZ - fromUnderrun };
|
||||
|
||||
|
@ -40,8 +40,6 @@ class OpenAlEngine : public AudioEngine {
|
||||
ALuint source;
|
||||
unsigned buffers;
|
||||
unsigned bufPos;
|
||||
mutable unsigned fromUnderrun;
|
||||
int rate;
|
||||
|
||||
void deleteProcessedBufs() const;
|
||||
int doInit(int rate, unsigned latency);
|
||||
|
@ -68,21 +68,23 @@ QT += opengl
|
||||
INCLUDEPATH += framework/SDL_Joystick/include
|
||||
INCLUDEPATH += $$COMMONPATH
|
||||
DEFINES += HAVE_STDINT_H
|
||||
QMAKE_CXXFLAGS += -fno-exceptions -fno-rtti
|
||||
|
||||
# QMAKE_CXXFLAGS = -g
|
||||
macx {
|
||||
HEADERS += $$COMMONPATH/ringbuffer.h
|
||||
SOURCES += framework/addaudioengines_macx.cpp \
|
||||
framework/addblitterwidgets.cpp \
|
||||
framework/getfullmodetoggler_macx.cpp
|
||||
SOURCES += framework/SDL_Joystick/src/darwin/SDL_sysjoystick.c \
|
||||
framework/audioengines/openalengine.cpp \
|
||||
framework/audioengines/coreaudioengine.cpp \
|
||||
framework/fullmodetogglers/quartztoggler.cpp
|
||||
HEADERS += framework/audioengines/openalengine.h \
|
||||
framework/audioengines/coreaudioengine.h \
|
||||
framework/fullmodetogglers/quartztoggler.h
|
||||
LIBS += -framework \
|
||||
IOKit \
|
||||
-framework \
|
||||
OpenAL
|
||||
LIBS += -framework IOKit \
|
||||
-framework AudioUnit \
|
||||
-framework OpenAL
|
||||
}
|
||||
else:unix {
|
||||
DEFINES += PLATFORM_UNIX
|
||||
|
@ -55,6 +55,10 @@ static SampleRateInfo generateSampleRateInfo() {
|
||||
srinfo.minCustomRate = 8000;
|
||||
srinfo.maxCustomRate = 192000;
|
||||
|
||||
#ifdef Q_WS_MAC
|
||||
srinfo.defaultRateIndex = 1;
|
||||
#endif
|
||||
|
||||
return srinfo;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
#ifndef AUDIODATA_H
|
||||
#define AUDIODATA_H
|
||||
|
||||
#include "ringbuffer.h"
|
||||
#include <ringbuffer.h>
|
||||
#include <rateest.h>
|
||||
#include <SDL.h>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user