0.引言
本篇文章主要是講解音訊採集模組的實現,從0到1實現,完整的還原整個過程。也可以參考前面的文章。參考文章列表如下:
1.建立專案工程
2.建立CommonLooper
新建一個c++ class為CommonLooper
CommonLooper.h原始碼如下:
CommonLooper.cpp原始碼如下:
3.建立mediabase
新建一個C++ Header File為mediabase.h,建立引數相關的工作。
mediabase.h原始碼如下:
#ifndef MEDIABASE_H#define MEDIABASE_H#include <map>#include <vector>#include <string>#include <string.h>enum RET_CODE{ RET_ERR_UNKNOWN = -2, // 未知錯誤 RET_FAIL = -1, // 失敗 RET_OK = 0, // 正常 RET_ERR_OPEN_FILE, // 開啟檔案失敗 RET_ERR_NOT_SUPPORT, // 不支援 RET_ERR_OUTOFMEMORY, // 沒有記憶體 RET_ERR_STACKOVERFLOW, // 溢位 RET_ERR_NULLREFERENCE, // 空參考 RET_ERR_ARGUMENTOUTOFRANGE, // RET_ERR_PARAMISMATCH, // RET_ERR_MISMATCH_CODE, // 沒有匹配的編解碼器 RET_ERR_EAGAIN, RET_ERR_EOF};class Properties: public std::map<std::string,std::string>{public: bool HasProperty(const std::string &key) const { return find(key)!=end(); } void SetProperty(const char* key,int intval) { SetProperty(std::string(key),std::to_string(intval)); } void SetProperty(const char* key,uint32_t val) { SetProperty(std::string(key),std::to_string(val)); } void SetProperty(const char* key,uint64_t val) { SetProperty(std::string(key),std::to_string(val)); } void SetProperty(const char* key,const char* val) { SetProperty(std::string(key),std::string(val)); } void SetProperty(const std::string &key,const std::string &val) { insert(std::pair<std::string,std::string>(key,val)); } void GetChildren(const std::string& path,Properties &children) const { //Create sarch string std::string parent(path); //Add the final . parent += "."; //For each property for (const_iterator it = begin(); it!=end(); ++it) { const std::string &key = it->first; //Check if it is from parent if (key.compare(0,parent.length(),parent)==0) //INsert it children.SetProperty(key.substr(parent.length(),key.length()-parent.length()),it->second); } } void GetChildren(const char* path,Properties &children) const { GetChildren(std::string(path),children); } Properties GetChildren(const std::string& path) const { Properties properties; //Get them GetChildren(path,properties); //Return return properties; } Properties GetChildren(const char* path) const { Properties properties; //Get them GetChildren(path,properties); //Return return properties; } void GetChildrenArray(const char* path,std::vector<Properties> &array) const { //Create sarch string std::string parent(path); //Add the final . parent += "."; //Get array length int length = GetProperty(parent+"length",0); //For each element for (int i=0; i<length; ++i) { char index[64]; //Print string snprintf(index,sizeof(index),"%d",i); //And get children array.push_back(GetChildren(parent+index)); } } const char* GetProperty(const char* key) const { return GetProperty(key,""); } std::string GetProperty(const char* key,const std::string defaultValue) const { //Find item const_iterator it = find(std::string(key)); //If not found if (it==end()) //return default return defaultValue; //Return value return it->second; } std::string GetProperty(const std::string &key,const std::string defaultValue) const { //Find item const_iterator it = find(key); //If not found if (it==end()) //return default return defaultValue; //Return value return it->second; } const char* GetProperty(const char* key,const char *defaultValue) const { //Find item const_iterator it = find(std::string(key)); //If not found if (it==end()) //return default return defaultValue; //Return value return it->second.c_str(); } const char* GetProperty(const std::string &key,char *defaultValue) const { //Find item const_iterator it = find(key); //If not found if (it==end()) //return default return defaultValue; //Return value return it->second.c_str(); } int GetProperty(const char* key,int defaultValue) const { return GetProperty(std::string(key),defaultValue); } int GetProperty(const std::string &key,int defaultValue) const { //Find item const_iterator it = find(key); //If not found if (it==end()) //return default return defaultValue; //Return value return atoi(it->second.c_str()); } uint64_t GetProperty(const char* key,uint64_t defaultValue) const { return GetProperty(std::string(key),defaultValue); } uint64_t GetProperty(const std::string &key,uint64_t defaultValue) const { //Find item const_iterator it = find(key); //If not found if (it==end()) //return default return defaultValue; //Return value return atoll(it->second.c_str()); } bool GetProperty(const char* key,bool defaultValue) const { return GetProperty(std::string(key),defaultValue); } bool GetProperty(const std::string &key,bool defaultValue) const { //Find item const_iterator it = find(key); //If not found if (it==end()) //return default return defaultValue; //Get value char * val = (char *)it->second.c_str(); //Check it if (strcasecmp(val,(char *)"yes")==0) return true; else if (strcasecmp(val,(char *)"true")==0) return true; //Return value return (atoi(val)); }};#endif // MEDIABASE_H
4.建立dlog
新增c++ class 為dlog,當做日誌庫使用。
dlog.h原始碼如下:
#ifndef LOG_H#define LOG_H#include <stdio.h>#ifndef NULL#define NULL (0)#endif#ifndef TRUE#define TRUE (1)#endif#ifndef FALSE#define FALSE (0)#endif#ifdef __cplusplusextern "C" {#endiftypedef enum _slog_level { S_TRACE = 1, S_DEBUG = 2, S_INFO = 3, S_WARN = 4, S_ERROR = 5} slog_level;int init_logger(const char *log_dir, slog_level level);void write_log(slog_level level, int print_stacktrace, const char *func_name, int line, const char *fmt, ...);#define LogError(fmt, ...) write_log(S_ERROR, FALSE, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)#define LogWarn(fmt, ...) write_log(S_WARN, FALSE, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)#define LogInfo(fmt, ...) write_log(S_INFO, FALSE, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)#define LogDebug(fmt, ...) write_log(S_DEBUG, FALSE, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)#define LogTrace(fmt, ...) write_log(S_TRACE, FALSE, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)#ifdef __cplusplus}#endif#endif // LOG_H
dlog.cpp原始碼如下:
#include "dlog.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include <stdarg.h>#include <stdint.h>#if defined(WIN32)#include <io.h>#include <direct.h>#include <Windows.h>#include <DbgHelp.h>#elif defined(linux)#include <unistd.h>#include <pthread.h>#include <sys/stat.h>#include <execinfo.h>#endif#define MAX_LEVEL_STR (10)#define MAX_DATE_STR (10)#define DATE_STR_FMT "%04d%02d%02d"#define MAX_TIME_STR (20)#define TIME_STR_FMT "%04d/%02d/%02d %02d:%02d:%02d"#define MAX_FILE_PATH (260)#define MAX_LOG_LINE (4096)#define INNER_DEEP (2)#define MAX_DEEP (24)#define MAX_ST_INFO (256)#define MAX_ST_LINE (512)#if defined(WIN32)#define snprintf _snprintf#define vsnprintf _vsnprintf#define PROC_HANDLE HANDLE#define SLOG_MUTEX CRITICAL_SECTION#elif defined(linux)#define PROC_HANDLE void *#define SLOG_MUTEX pthread_mutex_t#endiftypedef struct _logger_cfg { PROC_HANDLE curr_proc; FILE *log_file; SLOG_MUTEX mtx; slog_level filter_levle; int inited;} logger_cfg;static logger_cfg g_logger_cfg = { NULL, NULL, {0}, S_INFO, FALSE };static void _slog_init_mutex(SLOG_MUTEX *mtx){#if defined(WIN32) InitializeCriticalSection(mtx);#elif defined(linux) pthread_mutex_init(mtx, NULL);#endif}static void _slog_lock(SLOG_MUTEX *mtx){#if defined(WIN32) EnterCriticalSection(mtx);#elif defined(linux) pthread_mutex_lock(mtx);#endif}static void _slog_unlock(SLOG_MUTEX *mtx){#if defined(WIN32) LeaveCriticalSection(mtx);#elif defined(linux) pthread_mutex_unlock(mtx);#endif}static void _get_curr_date(int datestr_size, char datestr[]){ time_t tt = { 0 }; struct tm *curr_time = NULL; time(&tt); curr_time = localtime(&tt); snprintf(datestr, datestr_size - 1, DATE_STR_FMT, curr_time->tm_year + 1900, curr_time->tm_mon + 1, curr_time->tm_mday);}static void _get_curr_time(int timestr_size, char timestr[]){ time_t tt = { 0 }; struct tm *curr_time = NULL; time(&tt); curr_time = localtime(&tt); snprintf(timestr, timestr_size - 1, TIME_STR_FMT, curr_time->tm_year + 1900, curr_time->tm_mon + 1, curr_time->tm_mday, curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec);}static inline char *_get_level_str(slog_level level){ switch (level) { case S_TRACE: return (char *)"[TRACE]"; case S_DEBUG: return (char *)"[DEBUG]"; case S_INFO: return (char *)"[INFO ]"; case S_WARN: return (char *)"[WARN ]"; case S_ERROR: return (char *)"[ERROR]"; default: return (char *)"[ ]"; }}static void _write_stacktrace(){ unsigned int i = 0; unsigned short frames = 0; void *stack[MAX_DEEP] = { 0 }; char st_line[MAX_ST_LINE] = { 0 };#if defined(WIN32)#elif defined(linux) char **st_arr = NULL; frames = backtrace(stack, MAX_DEEP); st_arr = backtrace_symbols(stack, frames); for (i = 0; i < frames; ++i) { snprintf(st_line, sizeof(st_line) - 1, " %d: %s\n", frames - i - 1, st_arr[i]); fwrite(st_line, sizeof(char), strlen(st_line), g_logger_cfg.log_file); } free(st_arr);#endif}static int _slog_mkdir(const char *log_dir){#if defined(WIN32) if (mkdir(log_dir) != 0) { return FALSE; }#elif defined(linux) if (mkdir(log_dir, 0744) != 0) { return FALSE; }#endif return TRUE;}static int _get_curr_proc_handle(){#if defined(WIN32) g_logger_cfg.curr_proc = NULL;#elif defined(linux) g_logger_cfg.curr_proc = NULL;#endif return TRUE;}int init_logger(const char *log_dir, slog_level level){ char log_filepath[MAX_FILE_PATH] = { 0 }; char datestr[MAX_DATE_STR] = { 0 }; if (TRUE == g_logger_cfg.inited) { return TRUE; } if (access(log_dir, 0) != 0) { if (_slog_mkdir(log_dir) != TRUE) { return FALSE; } } _slog_init_mutex(&g_logger_cfg.mtx); _get_curr_proc_handle(); _get_curr_date(sizeof(datestr), datestr); snprintf(log_filepath, sizeof(log_filepath) - 1, "%s/%s.log", log_dir, datestr); g_logger_cfg.log_file = fopen(log_filepath, "w+"); if (NULL == g_logger_cfg.log_file) { return FALSE; } g_logger_cfg.filter_levle = level; g_logger_cfg.inited = TRUE; return TRUE;}static inline int64_t getTimeMillisecond(){ #ifdef _WIN32 return (int64_t)GetTickCount(); #else struct timeval tv; gettimeofday(&tv, NULL); return ((int64_t)tv.tv_sec * 1000 + (unsigned long long)tv.tv_usec / 1000); #endif// return duration_cast<chrono::milliseconds>(high_resolution_clock::now() - m_begin).count();}void write_log(slog_level level, int print_stacktrace, const char *func_name, int line, const char *fmt, ...){ va_list args; char *level_str = NULL; char timestr[MAX_TIME_STR] = { 0 }; char log_content[MAX_LOG_LINE] = { 0 }; char log_line[MAX_LOG_LINE] = { 0 }; if (g_logger_cfg.filter_levle > level) { return; } va_start(args, fmt); vsnprintf(log_content, sizeof(log_content) - 1, fmt, args); va_end(args); _get_curr_time(sizeof(timestr), timestr); level_str = _get_level_str(level); int64_t cur_time = getTimeMillisecond(); snprintf(log_line, sizeof(log_line) - 1, "[%s %s-%d %s:%d] %s\n", level_str, timestr, int(cur_time%1000), func_name, line, log_content);// _slog_lock(&g_logger_cfg.mtx); fwrite(log_line, sizeof(char), strlen(log_line), g_logger_cfg.log_file);// if (TRUE == print_stacktrace) {// _write_stacktrace();// } fflush(g_logger_cfg.log_file); printf("%s", log_line); // 先列印到終端再說// _slog_unlock(&g_logger_cfg.mtx);}
5.建立音訊採集
新增c++ class 為AudioCapturer,當做音訊採集使用。
AudioCapturer.h原始碼如下:
#ifndef AUDIOCAPTURER_H#define AUDIOCAPTURER_H#include <functional>#include "commonlooper.h"#include "mediabase.h"using std::function;class AudioCapturer:public CommonLooper{public: AudioCapturer(); virtual ~AudioCapturer(); RET_CODE Init(const Properties properties); virtual void Loop(); void AddCallback(function<void(uint8_t *, int32_t)> callback);private: // PCM file只是用來測試, 寫死為s16格式 2通道 取樣率48Khz // 1幀1024取樣點持續的時間21.333333333333333333333333333333ms int openPcmFile(const char *file_name); int readPcmFile(uint8_t *pcm_buf, int32_t pcm_buf_size); int closePcmFile(); int audio_test_ = 0; std::string input_pcm_name_; //輸入的pcm測試檔名字 FILE *pcm_fp_ = NULL; int64_t pcm_start_time_ = 0; //起始時間 double pcm_total_duration_ = 0; //推流時長的統計 double frame_duration_ = 23.2; std::function<void(uint8_t *, int32_t)> callback_get_pcm_; //const int PCM_BUF_MAX_SIZE = 8196; bool is_first_time_ = false; int sample_rate_ = 48000; int nb_samples_ = 1024; int format_ = 1;//目前固定s16 int channels_ = 2; int32_t pcm_buf_size_; uint8_t *pcm_buf_;};#endif // AUDIOCAPTURER_H
AudioCapturer.cpp原始碼如下:
#include "audiocapturer.h"#include "dlog.h"#include "timesutil.h"AudioCapturer::AudioCapturer(){}AudioCapturer::~AudioCapturer(){}RET_CODE AudioCapturer::Init(const Properties properties){ audio_test_ = properties.GetProperty("audio_test", 0); input_pcm_name_ = properties.GetProperty("input_pcm_name", "buweishui_48000_2_s16le.pcm"); sample_rate_ = properties.GetProperty("sample_rate", 48000); channels_ = properties.GetProperty("channels",2); nb_samples_ = properties.GetProperty("nb_samples", 1024); pcm_buf_size_ = 2*channels_*nb_samples_; pcm_buf_ = new uint8_t[pcm_buf_size_];//分配buff if(!pcm_buf_) { return RET_ERR_OUTOFMEMORY; } if(openPcmFile(input_pcm_name_.c_str()) < 0) { LogError("openPcmFile %s failed", input_pcm_name_.c_str()); return RET_FAIL; } frame_duration_ = 1.0*nb_samples_/sample_rate_*1000;//單位就是ms,得到一幀的毫秒時間 return RET_OK;}void AudioCapturer::Loop(){ LogInfo("into loop"); pcm_total_duration_ = 0; pcm_start_time_ = TimesUtil::GetTimeMillisecond();//初始化時間基 while (true) { if(request_abort_) { break;//請求退出 } if(readPcmFile(pcm_buf_, pcm_buf_size_) == 0) { //如果是第一幀資料 if(is_first_time_) { is_first_time_ = true; LogInfo("is_first_time_"); } if(callback_get_pcm_) { //透過回撥把資料傳送出去給使用者去處理,對接的就是使用者的PushWork::PcmCallback callback_get_pcm_(pcm_buf_,pcm_buf_size_); } } //如果讀取的返回值不為0,就休眠2ms std::this_thread::sleep_for(std::chrono::milliseconds(2)); } request_abort_ = false; closePcmFile();}void AudioCapturer::AddCallback(function<void (uint8_t *, int32_t)> callback){ callback_get_pcm_ = callback;}int AudioCapturer::openPcmFile(const char *file_name){ pcm_fp_ = fopen(file_name, "rb"); if(!pcm_fp_) { return -1; } return 0;}int AudioCapturer::readPcmFile(uint8_t *pcm_buf, int32_t pcm_buf_size){ int64_t cur_time = TimesUtil::GetTimeMillisecond(); //單位毫秒 int64_t dif = cur_time - pcm_start_time_;//目前經過的時間 if(((int64_t)pcm_total_duration_) > dif) { return 1; //還沒有到讀取新一幀的時間 }//讀取資料 size_t ret = fread(pcm_buf, 1, pcm_buf_size, pcm_fp_); if(ret != pcm_buf_size) { //重新開始 ret = fseek(pcm_fp_,0, SEEK_SET); ret = fread(pcm_buf, 1, pcm_buf_size, pcm_fp_); if(ret != pcm_buf_size) { return -1;//出錯 } } //累計讀取幀時間 pcm_total_duration_ += frame_duration_; return 0;}int AudioCapturer::closePcmFile(){ if(pcm_fp_) { fclose(pcm_fp_); } return 0;}
6.建立時間基
新建一個C++ Header File為TimesUtil,當做時間類使用。
TimesUtil.h原始碼如下:
#ifndef TIMESUTIL_H#define TIMESUTIL_H#include <stdint.h>#ifdef _WIN32#include <winsock2.h>#include <ws2tcpip.h>#ifdef _MSC_VER /* MSVC *///#define snprintf _snprintf#define strcasecmp stricmp#define strncasecmp strnicmp//#define vsnprintf _vsnprintf#endif#define GetSockError() WSAGetLastError()#define SetSockError(e) WSASetLastError(e)#define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e)#define EWOULDBLOCK WSAETIMEDOUT /* we don't use nonblocking, but we do use timeouts */#define sleep(n) Sleep(n*1000)#define msleep(n) Sleep(n)#define SET_RCVTIMEO(tv,s) int tv = s*1000#else /* !_WIN32 */#include <sys/types.h>#include <sys/socket.h>#include <sys/times.h>#include <netdb.h>#include <unistd.h>#include <netinet/in.h>#include <netinet/tcp.h>#include <arpa/inet.h>#define GetSockError() errno#define SetSockError(e) errno = e#undef closesocket#define closesocket(s) close(s)#define msleep(n) usleep(n*1000)#define SET_RCVTIMEO(tv,s) struct timeval tv = {s,0}#endif#include<chrono>using namespace std;using namespace std::chrono;#ifdef _WIN32#pragma comment(lib, "ws2_32.lib")#endifclass TimesUtil{public: static inline int64_t GetTimeMillisecond() { #ifdef _WIN32 return (int64_t)GetTickCount(); #else struct timeval tv; gettimeofday(&tv, NULL); return ((int64_t)tv.tv_sec * 1000 + (unsigned long long)tv.tv_usec / 1000); #endif// return duration_cast<chrono::milliseconds>(high_resolution_clock::now() - m_begin).count(); }//private:// static time_point<high_resolution_clock> m_begin;};#endif // TIMESUTIL_H
怎麼按照播放速率讀取時間?
當前時間減去時間基準,幀累計時間計算
7.建立使用者管理推流模組
新增c++ class 為push_work,實現一個push_work,當做一個總的模組,開放出去使用。
pushwork.h原始碼如下:
#include "pushwork.h"#include "dlog.h"#include <functional>PushWork::PushWork(){}RET_CODE PushWork::Init(const Properties &properties){ // 音訊test模式 audio_test_ = properties.GetProperty("audio_test", 0); input_pcm_name_ = properties.GetProperty("input_pcm_name", "input_48k_2ch_s16.pcm"); // 麥克風取樣屬性 mic_sample_rate_ = properties.GetProperty("mic_sample_rate", 48000); mic_sample_fmt_ = properties.GetProperty("mic_sample_fmt", AV_SAMPLE_FMT_S16); mic_channels_ = properties.GetProperty("mic_channels", 2); //設定音訊捕獲 audio_capturer_ = new AudioCapturer(); Properties aud_cap_properties; aud_cap_properties.SetProperty("audio_test", 1); aud_cap_properties.SetProperty("input_pcm_name", input_pcm_name_); aud_cap_properties.SetProperty("nb_channels", mic_channels_); //aud_cap_properties.SetProperty("nb_samples",1024); if(audio_capturer_->Init(aud_cap_properties) != RET_OK) { LogError("AudioCapturer Init failed"); return RET_FAIL; } //呼叫回撥函式 audio_capturer_->AddCallback(std::bind(&PushWork::PcmCallback, this, std::placeholders::_1, std::placeholders::_2)); if(audio_capturer_->Start() != RET_OK) { LogError("AudioCapturer Start failed"); return RET_FAIL; } return RET_OK;}RET_CODE PushWork::DeInit(){ if(audio_capturer_) { audio_capturer_->Stop(); delete audio_capturer_; audio_capturer_ = NULL; }}//使用者的回撥函式實現void PushWork::PcmCallback(uint8_t *pcm, int32_t size){ LogInfo("size:%d",size);}
pushwork.cpp原始碼如下:
#include "pushwork.h"#include "dlog.h"#include <functional>PushWork::PushWork(){}RET_CODE PushWork::Init(const Properties &properties){ // 音訊test模式 audio_test_ = properties.GetProperty("audio_test", 0); input_pcm_name_ = properties.GetProperty("input_pcm_name", "input_48k_2ch_s16.pcm"); // 麥克風取樣屬性 mic_sample_rate_ = properties.GetProperty("mic_sample_rate", 48000); mic_sample_fmt_ = properties.GetProperty("mic_sample_fmt", AV_SAMPLE_FMT_S16); mic_channels_ = properties.GetProperty("mic_channels", 2); //設定音訊捕獲 audio_capturer_ = new AudioCapturer(); Properties aud_cap_properties; aud_cap_properties.SetProperty("audio_test", 1); aud_cap_properties.SetProperty("input_pcm_name", input_pcm_name_); aud_cap_properties.SetProperty("nb_channels", mic_channels_); //aud_cap_properties.SetProperty("nb_samples",1024); if(audio_capturer_->Init(aud_cap_properties) != RET_OK) { LogError("AudioCapturer Init failed"); return RET_FAIL; } //呼叫回撥函式 audio_capturer_->AddCallback(std::bind(&PushWork::PcmCallback, this, std::placeholders::_1, std::placeholders::_2)); if(audio_capturer_->Start() != RET_OK) { LogError("AudioCapturer Start failed"); return RET_FAIL; } return RET_OK;}RET_CODE PushWork::DeInit(){ if(audio_capturer_) { audio_capturer_->Stop(); delete audio_capturer_; audio_capturer_ = NULL; }}//使用者的回撥函式實現void PushWork::PcmCallback(uint8_t *pcm, int32_t size){ LogInfo("size:%d",size);}
8.建立main函式
主函式main.cpp原始碼如下:
#include <iostream>#include "dlog.h"#include "pushwork.h"using namespace std;extern "C" {#include <libavcodec/avcodec.h>#include <libswresample/swresample.h>#include <libavutil/opt.h>#include <libavutil/audio_fifo.h>}//第一版的音訊採集,已實現int main(){ cout << "Hello World!" << endl; init_logger("rtsp_push.log", S_INFO); { PushWork push_work; Properties properties; // 音訊test模式 properties.SetProperty("audio_test", 1); // 音訊測試模式 properties.SetProperty("input_pcm_name", "buweishui_48000_2_s16le.pcm"); // 麥克風取樣屬性 properties.SetProperty("mic_sample_fmt", AV_SAMPLE_FMT_S16); properties.SetProperty("mic_sample_rate", 48000); properties.SetProperty("mic_channels", 2); if(push_work.Init(properties) != RET_OK) { LogError("PushWork init failed"); return -1; } int count = 0; while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1000));//10s鍾退出 if(count++ > 5) break; } } LogInfo("main finish"); return 0;}
9.測試
音訊採集除錯完成,可以讀到資料。
注意:需要提前準備好音訊pcm檔案。
10.總結
最新評論