OpenAL播放pcm或wav数据流-windows/ios/android(一)
2023-09-27 14:29:30 时间
* Function
* OpenAL through the buffer queuing mechanism to support the streaming playback of sound. The buffer queue is a buffer associated with a single source contact mechanism.
* when audio playback, continuous rendering of each buffer, as if the buffer is composed of a continuous sound. This can be controlled by some special functions.
* flow is generally the source of the work. In a number of audio buffer by alSourceQueueBuffers () function to queue, and then play the sound source,
* next with property AL_BUFFERS_PROCESSED to query. This property obtains the number of buffers that have been processed,
* allows applications to use the alSourceUnqueueBuffers () function to delete the buffers that have been processed.
* alSourceUnqueueBuffers () function will start from the queue header will be processed in order to remove the buffer. Finally, the rest of the buffer queue in gear.
* Opanal for audio rendering related implementation and definition, etc.
* OpenAL通过缓冲器排队机制支持声音的流式播放。缓冲器排队是多个缓冲器与单一音源相关联的一种机制。
* 当音源播放时,连续对各个缓冲器进行渲染,就好象这些缓冲器组成了一个连续的声音。这可以通过一些特殊函数来控制。
* 流音源的工作一般是这样的。音源里的一批缓冲器通过alSourceQueueBuffers()函数进行排队,然后播放音源,
* 接下来用属性AL_BUFFERS_PROCESSED来查询。该属性得出已经处理好的缓冲器的数量,
* 从而允许应用程序使用alSourceUnqueueBuffers()函数删除那些已经处理好的缓冲器。
* alSourceUnqueueBuffers()函数将从队列头部开始依次将处理好的缓冲器删除。最后,其余的缓冲器在音源上排队。
* OpanAl 用于音频渲染相关实现及定义,等
#ifndef __LVS_OPENAL_INTERFACE_H__
#define __LVS_OPENAL_INTERFACE_H__
#include stdio.h
#include stdlib.h
#include string
//windows
#ifdef WIN32
#include Windows.h
//openAl库
#include "alut.h"
#pragma comment(lib,"alut.lib")
#pragma comment(lib,"OpenAL32.lib")
//ios
#elif __APPLE__
#include "alut.h"
//ANDROID平台
#elif __ANDROID__
#include "alut.h"
//linux
#else
#include "alut.h"
#endif
//到处宏定义
//windows
#ifdef WIN32
#define LVS_DLLEXPORT __declspec(dllexport)
//ios
#elif __APPLE__
#define LVS_DLLEXPORT
//linux
#else
#define LVS_DLLEXPORT
#endif
using namespace std;
//接口初始化
int lvs_openal_interface_init();
//接口释放
void lvs_openal_interface_uninit();
//接口开始播放
void lvs_openal_interface_playsound();
//接口停止播放
void lvs_openal_interface_stopsound();
//接口设置音量
void lvs_openal_interface_setvolume(float volume);//volume取值范围(0~1)
//接口获取音量
float lvs_openal_interface_getvolume();
//接口传入pcm数据用于播放
int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
//更新队列数据,删除已经播放的buffer,这个在队列满的时候用
int lvs_openal_interface_updataQueueBuffer();
//获取当前时间戳
long long lvs_openal_interface_getrealpts();
//获取已经播放了多少个数据块
long long lvs_openal_interface_getIsplayBufferSize();
//获取缓存队列长度
int lvs_openal_getnumqueuedsize();
class cclass_openal_interface;
class cclass_openal_interface
public:
cclass_openal_interface();
virtual ~cclass_openal_interface();
//开始播放
void playSound();
//停止播放
void stopSound();
//设置音量
void SetVolume(float volume);//volume取值范围(0~1)
//获取音量
float GetVolume();
//传入pcm数据用于播放
int openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
//更新队列数据,删除已经播放的buffer
int updataQueueBuffer();
private:
//初始化openal
int initOpenAL();
//释放openal
void cleanUpOpenAL();
public:
int m_numprocessed; //队列中已经播放过的数量
int m_numqueued; //队列中缓冲队列数量
long long m_IsplayBufferSize; //已经播放了多少个音频缓存数目
double m_oneframeduration; //一帧音频数据持续时间(ms)
float m_volume; //当前音量volume取值范围(0~1)
int m_samplerate; //采样率
int m_bit; //样本值
int m_channel; //声道数
int m_datasize; //一帧音频数据量
private:
ALCdevice * m_Devicde; //device句柄
ALCcontext * m_Context; //device context
ALuint m_outSourceId; //source id 负责播放
#endif
int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel) return copenal_interface- openAudioFromQueue(data,dataSize,aSampleRate,aBit,aChannel); long long lvs_openal_interface_getrealpts() long long time = (long long )((copenal_interface- m_IsplayBufferSize * copenal_interface- m_oneframeduration) + 0.5); printf("*****m_IsplayBufferSize : %ld",copenal_interface- m_IsplayBufferSize); printf("****************time : %lld(ms)\n",time); return time; long long lvs_openal_interface_getIsplayBufferSize() return copenal_interface- m_IsplayBufferSize; int lvs_openal_getnumqueuedsize() return copenal_interface- m_numqueued; int lvs_openal_interface_updataQueueBuffer() return copenal_interface- updataQueueBuffer(); cclass_openal_interface::cclass_openal_interface() m_Devicde = NULL; m_Context = NULL; m_outSourceId = 0; m_numprocessed = 0; m_numqueued = 0; m_IsplayBufferSize = 0; m_oneframeduration = 0.0; m_volume = 1.0; m_samplerate = 0; m_bit = 0; m_channel = 0; m_datasize = 0; //init initOpenAL(); cclass_openal_interface::~cclass_openal_interface() cleanUpOpenAL(); m_Devicde = NULL; m_Context = NULL; m_outSourceId = 0; m_numprocessed = 0; m_numqueued = 0; m_IsplayBufferSize = 0; m_oneframeduration = 0.0; m_volume = 1.0; m_samplerate = 0; m_bit = 0; m_channel = 0; m_datasize = 0; int cclass_openal_interface::initOpenAL() int ret = 0; printf("=======initOpenAl===\n"); #ifdef WIN32 //初始化 ALUT openal函数库 int zwg_argc=1; //添加函数库名称 char* zwg_argv[]={"ZWG_ALUT"}; ret= alutInit( zwg_argc, zwg_argv); #else #endif //打开device m_Devicde = alcOpenDevice(NULL); if (m_Devicde) { #ifdef WIN32 //windows 用这个context 声音不正常,以后处理 #else //建立声音文本描述 m_Context = alcCreateContext(m_Devicde, NULL); //设置行为文本描述 alcMakeContextCurrent(m_Context); #endif } //创建一个source并设置一些属性 alGenSources(1, m_outSourceId); alSpeedOfSound(1.0); alDopplerVelocity(1.0); alDopplerFactor(1.0); alSourcef(m_outSourceId, AL_PITCH, 1.0f); alSourcef(m_outSourceId, AL_GAIN, 1.0f); alSourcei(m_outSourceId, AL_LOOPING, AL_FALSE); alSourcef(m_outSourceId, AL_SOURCE_TYPE, AL_STREAMING); return ret; void cclass_openal_interface::cleanUpOpenAL() printf("=======cleanUpOpenAL===\n"); alDeleteSources(1, m_outSourceId); #ifdef WIN32 alcCloseDevice(m_Devicde); m_Devicde = NULL; alutExit(); #else ALCcontext * Context = alcGetCurrentContext(); ALCdevice * Devicde = alcGetContextsDevice(Context); if (Context) { alcMakeContextCurrent(NULL); alcDestroyContext(Context); m_Context = NULL; } alcCloseDevice(m_Devicde); m_Devicde = NULL; #endif void cclass_openal_interface::playSound() int ret = 0; alSourcePlay(m_outSourceId); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alcMakeContextCurrent %x : %s\n", ret,alutGetErrorString (ret)); } void cclass_openal_interface::stopSound() alSourceStop(m_outSourceId); void cclass_openal_interface::SetVolume(float volume)//volume取值范围(0~1) m_volume = volume; alSourcef(m_outSourceId,AL_GAIN,volume); float cclass_openal_interface::GetVolume() return m_volume; int cclass_openal_interface::updataQueueBuffer() //播放状态字段 ALint stateVaue = 0; //获取处理队列,得出已经播放过的缓冲器的数量 alGetSourcei(m_outSourceId, AL_BUFFERS_PROCESSED, m_numprocessed); //获取缓存队列,缓存的队列数量 alGetSourcei(m_outSourceId, AL_BUFFERS_QUEUED, m_numqueued); //获取播放状态,是不是正在播放 alGetSourcei(m_outSourceId, AL_SOURCE_STATE, stateVaue); //printf("===statevaue ========================%x\n",stateVaue); if (stateVaue == AL_STOPPED || stateVaue == AL_PAUSED || stateVaue == AL_INITIAL) { //如果没有数据,或数据播放完了 if (m_numqueued m_numprocessed || m_numqueued == 0 ||(m_numqueued == 1 m_numprocessed ==1)) { //停止播放 printf("...Audio Stop\n"); stopSound(); cleanUpOpenAL(); return 0; } if (stateVaue != AL_PLAYING) { playSound(); } } //将已经播放过的的数据删除掉 while(m_numprocessed --) { ALuint buff; //更新缓存buffer中的数据到source中 alSourceUnqueueBuffers(m_outSourceId, 1, buff); //删除缓存buff中的数据 alDeleteBuffers(1, buff); //得到已经播放的音频队列多少块 m_IsplayBufferSize ++; } long long time = (long long )((m_IsplayBufferSize * m_oneframeduration) + 0.5); //printf("*****m_IsplayBufferSize : %ld",m_IsplayBufferSize); //printf("****************time : %ld(ms)\n",time); return 1; int cclass_openal_interface::openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel) int ret = 0; //样本数openal的表示方法 ALenum format = 0; //buffer id 负责缓存,要用局部变量每次数据都是新的地址 ALuint bufferID = 0; if (m_datasize == 0 m_samplerate == 0 m_bit == 0 m_channel == 0) { if (dataSize != 0 aSampleRate != 0 aBit != 0 aChannel != 0) { m_datasize = dataSize; m_samplerate = aSampleRate; m_bit = aBit; m_channel = aChannel; m_oneframeduration = m_datasize * 1.0 /(m_bit/8) /m_channel /m_samplerate * 1000 ; //计算一帧数据持续时间 } } //创建一个buffer alGenBuffers(1, bufferID); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alGenBuffers %x : %s\n", ret,alutGetErrorString (ret)); //AL_ILLEGAL_ENUM //AL_INVALID_VALUE //#define AL_ILLEGAL_COMMAND 0xA004 //#define AL_INVALID_OPERATION 0xA004 } if (aBit == 8) { if (aChannel == 1) { format = AL_FORMAT_MONO8; } else if(aChannel == 2) { format = AL_FORMAT_STEREO8; } } if( aBit == 16 ) { if( aChannel == 1 ) { format = AL_FORMAT_MONO16; } if( aChannel == 2 ) { format = AL_FORMAT_STEREO16; } } //指定要将数据复制到缓冲区中的数据 alBufferData(bufferID, format, data, dataSize,aSampleRate); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alBufferData %x : %s\n", ret,alutGetErrorString (ret)); //AL_ILLEGAL_ENUM //AL_INVALID_VALUE //#define AL_ILLEGAL_COMMAND 0xA004 //#define AL_INVALID_OPERATION 0xA004 } //附加一个或一组buffer到一个source上 alSourceQueueBuffers(m_outSourceId, 1, bufferID); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alSourceQueueBuffers %x : %s\n", ret,alutGetErrorString (ret)); } //更新队列数据 ret = updataQueueBuffer(); //删除一个缓冲 这里不应该删除缓冲,在source里面播放完毕删除 //alDeleteBuffers(1, bufferID); bufferID = 0; return 1;
//要显示的pcm/wav文件路径及名称 #define PCM_STREAM_PATH_NAME "../pcm_stream/44100_2_16.pcm" int main() int ret = 0; int nSampleRate = 44100; //采样率 int nBit = 16; //样本数 int nChannel = 2; //声道 int ndatasize = 1024 * (nBit/8) *nChannel; //每次读取的数据大小 char ndata[4096 + 1] = {0}; //读取的数据 FILE * pFile_pcm = NULL; //读取pcm数据的文件句柄 //打开pcm文件 if((pFile_pcm = fopen(PCM_STREAM_PATH_NAME, "rb")) == NULL) { printf("filed open file : %s\n",PCM_STREAM_PATH_NAME); return getchar(); } else { printf("success open file : %s\n",PCM_STREAM_PATH_NAME); } //init lvs_openal_interface_init(); //设置音量volume取值范围(0~1) lvs_openal_interface_setvolume(1.0); for(;;) { Sleep(23); //循环读取文件 ret = fread(ndata, 1,ndatasize, pFile_pcm); if (ret != ndatasize) { //seek到文件开头 fseek(pFile_pcm, 0, SEEK_SET); fread(ndata, 1,ndatasize, pFile_pcm); } //具体的处理在这里 ret = lvs_openal_interface_openaudiofromqueue((char *)ndata,ndatasize,nSampleRate,nBit,nChannel); long long time = lvs_openal_interface_getrealpts(); } //uinit lvs_openal_interface_uninit(); //关闭pcm文件 if (pFile_pcm != NULL) { fclose(pFile_pcm); pFile_pcm = NULL; } return 1;
程序运行效果并能听到声音:
Application Loader及Transporter App上传ipa外、可以在Windows上架iOS APP工具 随着xcode的更新,苹果公司已经不直接提供Application Loader这个工具上传IPA了,导致上传ipa比较难搞了。 这里分享介绍一个可以在Windows、跨平台申请iOS证书上传ipa的工具Appuploader,方面跨平台开发没有苹果电脑,或者还不熟悉iOS上架流程的开发者使用。 双重验证码登录,安全放心,已帮助上万开发者提交苹果APP!
windows电脑在线创建ios证书的教程 目前苹果官网提供的,生成ios打包证书的方法,只能使用mac电脑,通过钥匙串访问的功能,生成证书,这种方法的缺点是你必须要有苹果mac电脑才能生成证书,而一个mac电脑,少则8000多元,成本比较高。 所以我们这篇文章,在这里教会大家如何在没有mac电脑的情况下,创建ios证书
Uni开发的app,使用Windows S10,放在ios上,全程跟着一个马平川。 其实在我写这篇文章的时候,很多版本的app都已经更新了,但是突然有一天我觉得如果我不把它们记录下来,它们就会被遗忘,现在越来越多的人在用uniapp开发app。
int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel) return copenal_interface- openAudioFromQueue(data,dataSize,aSampleRate,aBit,aChannel); long long lvs_openal_interface_getrealpts() long long time = (long long )((copenal_interface- m_IsplayBufferSize * copenal_interface- m_oneframeduration) + 0.5); printf("*****m_IsplayBufferSize : %ld",copenal_interface- m_IsplayBufferSize); printf("****************time : %lld(ms)\n",time); return time; long long lvs_openal_interface_getIsplayBufferSize() return copenal_interface- m_IsplayBufferSize; int lvs_openal_getnumqueuedsize() return copenal_interface- m_numqueued; int lvs_openal_interface_updataQueueBuffer() return copenal_interface- updataQueueBuffer(); cclass_openal_interface::cclass_openal_interface() m_Devicde = NULL; m_Context = NULL; m_outSourceId = 0; m_numprocessed = 0; m_numqueued = 0; m_IsplayBufferSize = 0; m_oneframeduration = 0.0; m_volume = 1.0; m_samplerate = 0; m_bit = 0; m_channel = 0; m_datasize = 0; //init initOpenAL(); cclass_openal_interface::~cclass_openal_interface() cleanUpOpenAL(); m_Devicde = NULL; m_Context = NULL; m_outSourceId = 0; m_numprocessed = 0; m_numqueued = 0; m_IsplayBufferSize = 0; m_oneframeduration = 0.0; m_volume = 1.0; m_samplerate = 0; m_bit = 0; m_channel = 0; m_datasize = 0; int cclass_openal_interface::initOpenAL() int ret = 0; printf("=======initOpenAl===\n"); #ifdef WIN32 //初始化 ALUT openal函数库 int zwg_argc=1; //添加函数库名称 char* zwg_argv[]={"ZWG_ALUT"}; ret= alutInit( zwg_argc, zwg_argv); #else #endif //打开device m_Devicde = alcOpenDevice(NULL); if (m_Devicde) { #ifdef WIN32 //windows 用这个context 声音不正常,以后处理 #else //建立声音文本描述 m_Context = alcCreateContext(m_Devicde, NULL); //设置行为文本描述 alcMakeContextCurrent(m_Context); #endif } //创建一个source并设置一些属性 alGenSources(1, m_outSourceId); alSpeedOfSound(1.0); alDopplerVelocity(1.0); alDopplerFactor(1.0); alSourcef(m_outSourceId, AL_PITCH, 1.0f); alSourcef(m_outSourceId, AL_GAIN, 1.0f); alSourcei(m_outSourceId, AL_LOOPING, AL_FALSE); alSourcef(m_outSourceId, AL_SOURCE_TYPE, AL_STREAMING); return ret; void cclass_openal_interface::cleanUpOpenAL() printf("=======cleanUpOpenAL===\n"); alDeleteSources(1, m_outSourceId); #ifdef WIN32 alcCloseDevice(m_Devicde); m_Devicde = NULL; alutExit(); #else ALCcontext * Context = alcGetCurrentContext(); ALCdevice * Devicde = alcGetContextsDevice(Context); if (Context) { alcMakeContextCurrent(NULL); alcDestroyContext(Context); m_Context = NULL; } alcCloseDevice(m_Devicde); m_Devicde = NULL; #endif void cclass_openal_interface::playSound() int ret = 0; alSourcePlay(m_outSourceId); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alcMakeContextCurrent %x : %s\n", ret,alutGetErrorString (ret)); } void cclass_openal_interface::stopSound() alSourceStop(m_outSourceId); void cclass_openal_interface::SetVolume(float volume)//volume取值范围(0~1) m_volume = volume; alSourcef(m_outSourceId,AL_GAIN,volume); float cclass_openal_interface::GetVolume() return m_volume; int cclass_openal_interface::updataQueueBuffer() //播放状态字段 ALint stateVaue = 0; //获取处理队列,得出已经播放过的缓冲器的数量 alGetSourcei(m_outSourceId, AL_BUFFERS_PROCESSED, m_numprocessed); //获取缓存队列,缓存的队列数量 alGetSourcei(m_outSourceId, AL_BUFFERS_QUEUED, m_numqueued); //获取播放状态,是不是正在播放 alGetSourcei(m_outSourceId, AL_SOURCE_STATE, stateVaue); //printf("===statevaue ========================%x\n",stateVaue); if (stateVaue == AL_STOPPED || stateVaue == AL_PAUSED || stateVaue == AL_INITIAL) { //如果没有数据,或数据播放完了 if (m_numqueued m_numprocessed || m_numqueued == 0 ||(m_numqueued == 1 m_numprocessed ==1)) { //停止播放 printf("...Audio Stop\n"); stopSound(); cleanUpOpenAL(); return 0; } if (stateVaue != AL_PLAYING) { playSound(); } } //将已经播放过的的数据删除掉 while(m_numprocessed --) { ALuint buff; //更新缓存buffer中的数据到source中 alSourceUnqueueBuffers(m_outSourceId, 1, buff); //删除缓存buff中的数据 alDeleteBuffers(1, buff); //得到已经播放的音频队列多少块 m_IsplayBufferSize ++; } long long time = (long long )((m_IsplayBufferSize * m_oneframeduration) + 0.5); //printf("*****m_IsplayBufferSize : %ld",m_IsplayBufferSize); //printf("****************time : %ld(ms)\n",time); return 1; int cclass_openal_interface::openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel) int ret = 0; //样本数openal的表示方法 ALenum format = 0; //buffer id 负责缓存,要用局部变量每次数据都是新的地址 ALuint bufferID = 0; if (m_datasize == 0 m_samplerate == 0 m_bit == 0 m_channel == 0) { if (dataSize != 0 aSampleRate != 0 aBit != 0 aChannel != 0) { m_datasize = dataSize; m_samplerate = aSampleRate; m_bit = aBit; m_channel = aChannel; m_oneframeduration = m_datasize * 1.0 /(m_bit/8) /m_channel /m_samplerate * 1000 ; //计算一帧数据持续时间 } } //创建一个buffer alGenBuffers(1, bufferID); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alGenBuffers %x : %s\n", ret,alutGetErrorString (ret)); //AL_ILLEGAL_ENUM //AL_INVALID_VALUE //#define AL_ILLEGAL_COMMAND 0xA004 //#define AL_INVALID_OPERATION 0xA004 } if (aBit == 8) { if (aChannel == 1) { format = AL_FORMAT_MONO8; } else if(aChannel == 2) { format = AL_FORMAT_STEREO8; } } if( aBit == 16 ) { if( aChannel == 1 ) { format = AL_FORMAT_MONO16; } if( aChannel == 2 ) { format = AL_FORMAT_STEREO16; } } //指定要将数据复制到缓冲区中的数据 alBufferData(bufferID, format, data, dataSize,aSampleRate); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alBufferData %x : %s\n", ret,alutGetErrorString (ret)); //AL_ILLEGAL_ENUM //AL_INVALID_VALUE //#define AL_ILLEGAL_COMMAND 0xA004 //#define AL_INVALID_OPERATION 0xA004 } //附加一个或一组buffer到一个source上 alSourceQueueBuffers(m_outSourceId, 1, bufferID); if((ret = alGetError()) != AL_NO_ERROR) { printf("error alSourceQueueBuffers %x : %s\n", ret,alutGetErrorString (ret)); } //更新队列数据 ret = updataQueueBuffer(); //删除一个缓冲 这里不应该删除缓冲,在source里面播放完毕删除 //alDeleteBuffers(1, bufferID); bufferID = 0; return 1;
//要显示的pcm/wav文件路径及名称 #define PCM_STREAM_PATH_NAME "../pcm_stream/44100_2_16.pcm" int main() int ret = 0; int nSampleRate = 44100; //采样率 int nBit = 16; //样本数 int nChannel = 2; //声道 int ndatasize = 1024 * (nBit/8) *nChannel; //每次读取的数据大小 char ndata[4096 + 1] = {0}; //读取的数据 FILE * pFile_pcm = NULL; //读取pcm数据的文件句柄 //打开pcm文件 if((pFile_pcm = fopen(PCM_STREAM_PATH_NAME, "rb")) == NULL) { printf("filed open file : %s\n",PCM_STREAM_PATH_NAME); return getchar(); } else { printf("success open file : %s\n",PCM_STREAM_PATH_NAME); } //init lvs_openal_interface_init(); //设置音量volume取值范围(0~1) lvs_openal_interface_setvolume(1.0); for(;;) { Sleep(23); //循环读取文件 ret = fread(ndata, 1,ndatasize, pFile_pcm); if (ret != ndatasize) { //seek到文件开头 fseek(pFile_pcm, 0, SEEK_SET); fread(ndata, 1,ndatasize, pFile_pcm); } //具体的处理在这里 ret = lvs_openal_interface_openaudiofromqueue((char *)ndata,ndatasize,nSampleRate,nBit,nChannel); long long time = lvs_openal_interface_getrealpts(); } //uinit lvs_openal_interface_uninit(); //关闭pcm文件 if (pFile_pcm != NULL) { fclose(pFile_pcm); pFile_pcm = NULL; } return 1;
程序运行效果并能听到声音:
本demo还需完善。
from:http://blog.csdn.net/zhuweigangzwg/article/details/53286945
Application Loader及Transporter App上传ipa外、可以在Windows上架iOS APP工具 随着xcode的更新,苹果公司已经不直接提供Application Loader这个工具上传IPA了,导致上传ipa比较难搞了。 这里分享介绍一个可以在Windows、跨平台申请iOS证书上传ipa的工具Appuploader,方面跨平台开发没有苹果电脑,或者还不熟悉iOS上架流程的开发者使用。 双重验证码登录,安全放心,已帮助上万开发者提交苹果APP!
windows电脑在线创建ios证书的教程 目前苹果官网提供的,生成ios打包证书的方法,只能使用mac电脑,通过钥匙串访问的功能,生成证书,这种方法的缺点是你必须要有苹果mac电脑才能生成证书,而一个mac电脑,少则8000多元,成本比较高。 所以我们这篇文章,在这里教会大家如何在没有mac电脑的情况下,创建ios证书
Uni开发的app,使用Windows S10,放在ios上,全程跟着一个马平川。 其实在我写这篇文章的时候,很多版本的app都已经更新了,但是突然有一天我觉得如果我不把它们记录下来,它们就会被遗忘,现在越来越多的人在用uniapp开发app。
相关文章
- js -- 客户端判断 浏览器判断 微信判断 ios android web判断
- Android开发之深入理解Android Studio构建文件build.gradle配置
- 用Jenkins自动化构建Android和iOS应用
- 【短视频SDK---FAQ】关于iOS的pod导入,Android的maven导入
- 【函数封装】javascript判断移动端操作系统为android 或 ios 或 iphoneX
- GitHub iOS 和 Android 正式发布!
- 2021大厂Android面试经历,知乎上已获万赞
- Android中Bitmap, Drawable, Byte之间的转化
- H5实现移动端禁止页面缩放(适用Android和IOS)
- 输出无名空数组---精android、IOS App应用服务程序开发
- Android 卡顿优化 1 卡顿解析
- Flutter环境配置(android)
- Android Toolbar如何上面添加menu
- 多语言翻译插件 支持android/IOS
- [android]在 Html.fromHtml 中的换行被忽略
- Android使用 httpClient取消http请求的方法
- android值入广告异常 java.lang.NoClassDefFoundError: com.google.ads.AdView
- android 沉浸式状态栏(像ios那样的状态栏与应用统一颜色样式)
- 小程序https Android 安卓可以发request请求,IOS 苹果 发请求失败问题
- react-native 实现条码扫描(ios&android)
- react-native-easy-toast, 一款简单易用的 Toast 组件,支持 Android&iOS.
- 基于React-Native的高仿「ONE·一个」,兼容Android、iOS双平台
- 基于react-native实现的博客园移动客户端,兼容android和ios
- React Native开源项目-稀土掘金客户端(Android、iOS双适配)
- Android Notifications通知
- 移动应用开发(IOS/android等)中一个通用的图片缓存方案讲解(附流程图)