音頻壓縮編碼 opus 附完整C++代碼示例

来源:https://www.cnblogs.com/tntmonks/archive/2018/04/18/8878392.html
-Advertisement-
Play Games

絕大數人都知道mp3格式編碼,以及aac,amr等壓縮格式編碼。 而在語音通信界有一個強悍的音頻格式編碼opus. 經過實測,壓縮比最高可以達到1:10。 100KB 壓縮後 10KB 雖然是有損壓縮, 但是根據實際對比試聽, 幾乎聽不出差別。 而且還原度還比mp3高,壓縮比也比mp3高。 用來壓縮 ...


絕大數人都知道mp3格式編碼,以及aac,amr等壓縮格式編碼。

而在語音通信界有一個強悍的音頻格式編碼opus.

經過實測,壓縮比最高可以達到1:10。

100KB 壓縮後 10KB

雖然是有損壓縮,

但是根據實際對比試聽,

幾乎聽不出差別。

而且還原度還比mp3高,壓縮比也比mp3高。

用來壓縮傳輸音頻,絕對是一大殺器。

 

項目官方地址:

https://opus-codec.org/

 

維基上的描述:

Opus是一個有損聲音編碼的格式,由Xiph.Org基金會開發,之後由互聯網工程任務組(IETF)進行標準化,目標用希望用單一格式包含聲音和語音,取代SpeexVorbis,且適用於網路上低延遲的即時聲音傳輸,標準格式定義於RFC 6716文件。Opus格式是一個開放格式,使用上沒有任何專利或限制。

Opus集成了兩種聲音編碼的技術:以語音編碼為導向的SILK和低延遲的CELT。Opus可以無縫調節高低比特率。在編碼器內部它在較低比特率時使用線性預測編碼在高比特率時候使用變換編碼(在高低比特率交界處也使用兩者結合的編碼方式)。Opus具有非常低的演算法延遲(預設為22.5 ms),非常適合用於低延遲語音通話的編碼,像是網路上的即時聲音流、即時同步聲音旁白等等,此外Opus也可以通過降低編碼比特率,達成更低的演算法延遲,最低可以到5 ms。在多個聽覺盲測中,Opus都比MP3AACHE-AAC等常見格式,有更低的延遲和更好的聲音壓縮率。

 

更重要的是要看這條:

Opus被提出用於在IETF上標準化新的音頻格式,最終被IETF編解碼器工作組接受和授予。它基於Xiph.Org基金會和Skype技術公司兩項最初分開的標準提案。其主要開發人員包括Jean-Marc Valin(Xiph.Org,Octasic,Mozilla Corporation),Koen Vos(Skype)和Timothy B. Terriberry(Xiph.Org,Mozilla Corporation)。其中包括Juin-Hwey(Raymond)Chen(Broadcom),Gregory Maxwell(Xiph.Org,Wikimedia)和Christopher Montgomery(Xiph.Org)也參與其中。

 

這個項目被幾大公司加持,也難怪能有如此出色的壓縮比。

 

由於是純C代碼,比較好理解。

抽了點空,俺寫個wav壓縮解壓的示例代碼,

分享出來,權當拋磚引玉。

示例代碼位置:

https://github.com/cpuimage/opus/blob/master/example/opus.cpp

貼上完整C++代碼:

#include <opus_types.h>
#include  <opus.h>
#include <cstring>
#include <memory>

#include <vector>
// https://github.com/mackron/dr_libs/blob/master/dr_wav.h
#define DR_WAV_IMPLEMENTATION

#include "dr_wav.h"

#define FRAME_SIZE 480
#define MAX_FRAME_SIZE (6*FRAME_SIZE)

#define MAX_CHANNELS 1
#define MAX_PACKET_SIZE (3*1276)

#pragma pack(push)
#pragma pack(1)

struct WavInfo {
    uint16_t channels;
    uint32_t sampleRate;
    uint32_t bitsPerSample;
};

#pragma pack(pop)

#ifndef  nullptr
#define  nullptr NULL
#endif

class FileStream {
public:
    FileStream() {
        cur_pos = 0;
    }

    void Append(const char *data, size_t size) {
        if (cur_pos + size > Size()) {
            vec.resize(cur_pos + size);
        }
        memcpy(vec.data() + cur_pos, data, size);
        cur_pos += size;
    }

    void AppendU32(uint32_t val) {
        Append((char *) (&val), sizeof(val));
    }

    char *Data() {
        return vec.data();
    }

    size_t Size() {
        return vec.size();
    }

    size_t Read(void *buff, size_t elemSize, size_t elemCount) {
        size_t readed = std::min((vec.size() - cur_pos), (elemCount * elemSize)) / elemSize;
        if (readed > 0) {
            memcpy(buff, vec.data() + cur_pos, readed * elemSize);
            cur_pos += readed * elemSize;
        }
        return readed;
    }

    bool SeekCur(int offset) {
        if (cur_pos + offset > vec.size()) {
            cur_pos = !vec.empty() ? (vec.size() - 1) : 0;
            return false;
        } else {
            cur_pos += offset;
            return true;
        }
    }

    bool SeekBeg(int offset = 0) {
        cur_pos = 0;
        return SeekCur(offset);
    }

    bool WriteToFile(const char *filename) {
        FILE *fin = fopen(filename, "wb");
        if (!fin) {
            return false;
        }
        fseek(fin, 0, SEEK_SET);
        fwrite(vec.data(), sizeof(char), vec.size(), fin);
        fclose(fin);
        return true;
    }

    bool ReadFromFile(const char *filename) {
        FILE *fin = fopen(filename, "rb");
        if (!fin) {
            return false;
        }
        fseek(fin, 0, SEEK_END);
        long fileSize = ftell(fin);
        vec.resize(static_cast<unsigned long long int>(fileSize));
        fseek(fin, 0, SEEK_SET);
        fread(vec.data(), sizeof(char), vec.size(), fin);
        fclose(fin);
        return true;
    }

private:
    std::vector<char> vec;
    size_t cur_pos;
};

bool Wav2Opus(FileStream *input, FileStream *output);

bool Opus2Wav(FileStream *input, FileStream *output);

bool wav2stream(char *input, FileStream *output);

bool stream2wav(FileStream *input, char *output);


bool wavWrite_int16(char *filename, int16_t *buffer, int sampleRate, uint32_t totalSampleCount) {
    drwav_data_format format = {};
    format.container = drwav_container_riff;     // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64.
    format.format = DR_WAVE_FORMAT_PCM;          // <-- Any of the DR_WAVE_FORMAT_* codes.
    format.channels = 1;
    format.sampleRate = (drwav_uint32) sampleRate;
    format.bitsPerSample = 16;
    drwav *pWav = drwav_open_file_write(filename, &format);
    if (pWav) {
        drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer);
        drwav_uninit(pWav);
        if (samplesWritten != totalSampleCount) {
            fprintf(stderr, "ERROR\n");
            return false;
        }
        return true;
    }
    return false;
}

int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount) {
    unsigned int channels;
    int16_t *buffer = drwav_open_and_read_file_s16(filename, &channels, sampleRate, totalSampleCount);
    if (buffer == nullptr) {
        fprintf(stderr, "ERROR\n");
        return nullptr;
    }
    if (channels != 1) {
        drwav_free(buffer);
        buffer = nullptr;
        *sampleRate = 0;
        *totalSampleCount = 0;
    }
    return buffer;
}

bool wav2stream(char *input, FileStream *output) {
    uint32_t sampleRate = 0;
    uint64_t totalSampleCount = 0;
    int16_t *wavBuffer = wavRead_int16(input, &sampleRate, &totalSampleCount);
    if (wavBuffer == nullptr) return false;
    WavInfo info = {};
    info.bitsPerSample = 16;
    info.sampleRate = sampleRate;
    info.channels = 1;
    output->SeekBeg();
    output->Append((char *) &info, sizeof(info));
    output->Append((char *) wavBuffer, totalSampleCount * sizeof(int16_t));
    free(wavBuffer);
    return true;
}

bool stream2wav(FileStream *input, char *output) {
    WavInfo info = {};
    input->SeekBeg();
    size_t read = input->Read(&info, sizeof(info), 1);
    if (read != 1) {
        return false;
    }
    size_t totalSampleCount = (input->Size() - sizeof(info)) / 2;
    return wavWrite_int16(output, (int16_t *) (input->Data() + sizeof(info)), info.sampleRate,
                          static_cast<uint32_t>(totalSampleCount));
}

bool Wav2Opus(FileStream *input, FileStream *output) {
    WavInfo in_info = {};
    input->SeekBeg();
    size_t read = input->Read(&in_info, sizeof(in_info), 1);
    if (read != 1) {
        return false;
    }
    uint32_t bitsPerSample = in_info.bitsPerSample;
    uint32_t sampleRate = in_info.sampleRate;
    uint16_t channels = in_info.channels;
    int err = 0;
    if (channels > MAX_CHANNELS) {
        return false;
    }
    OpusEncoder *encoder = opus_encoder_create(sampleRate, channels, OPUS_APPLICATION_AUDIO, &err);
    if (!encoder || err < 0) {
        fprintf(stderr, "failed to create an encoder: %s\n", opus_strerror(err));
        if (!encoder) {
            opus_encoder_destroy(encoder);
        }
        return false;
    }
    const uint16_t *data = (uint16_t *) (input->Data() + sizeof(in_info));
    size_t size = (input->Size() - sizeof(in_info)) / 2;
    opus_int16 pcm_bytes[FRAME_SIZE * MAX_CHANNELS];
    size_t index = 0;
    size_t step = static_cast<size_t>(FRAME_SIZE * channels);
    FileStream encodedData;
    unsigned char cbits[MAX_PACKET_SIZE];
    size_t frameCount = 0;
    size_t readCount = 0;
    while (index < size) {
        memset(&pcm_bytes, 0, sizeof(pcm_bytes));
        if (index + step <= size) {
            memcpy(pcm_bytes, data + index, step * sizeof(uint16_t));
            index += step;
        } else {
            readCount = size - index;
            memcpy(pcm_bytes, data + index, (readCount) * sizeof(uint16_t));
            index += readCount;
        }
        int nbBytes = opus_encode(encoder, pcm_bytes, channels * FRAME_SIZE, cbits, MAX_PACKET_SIZE);
        if (nbBytes < 0) {
            fprintf(stderr, "encode failed: %s\n", opus_strerror(nbBytes));
            break;
        }
        ++frameCount;
        encodedData.AppendU32(static_cast<uint32_t>(nbBytes));
        encodedData.Append((char *) cbits, static_cast<size_t>(nbBytes));
    }
    WavInfo info = {};
    info.bitsPerSample = bitsPerSample;
    info.sampleRate = sampleRate;
    info.channels = channels;
    output->SeekBeg();
    output->Append((char *) &info, sizeof(info));
    output->Append(encodedData.Data(), encodedData.Size());
    opus_encoder_destroy(encoder);
    return true;
}

bool Opus2Wav(FileStream *input, FileStream *output) {
    WavInfo info = {};
    input->SeekBeg();
    size_t read = input->Read(&info, sizeof(info), 1);
    if (read != 1) {
        return false;
    }
    int channels = info.channels;
    if (channels > MAX_CHANNELS) {
        return false;
    }
    output->SeekBeg();
    output->Append((char *) &info, sizeof(info));
    int err = 0;
    OpusDecoder *decoder = opus_decoder_create(info.sampleRate, channels, &err);
    if (!decoder || err < 0) {
        fprintf(stderr, "failed to create decoder: %s\n", opus_strerror(err));
        if (!decoder) {
            opus_decoder_destroy(decoder);
        }
        return false;
    }
    unsigned char cbits[MAX_PACKET_SIZE];
    opus_int16 out[MAX_FRAME_SIZE * MAX_CHANNELS];
    int frameCount = 0;
    while (true) {
        uint32_t nbBytes;
        size_t readed = input->Read(&nbBytes, sizeof(uint32_t), 1);
        if (readed == 0) {
            break;
        }

        if (nbBytes > sizeof(cbits)) {
            fprintf(stderr, "nbBytes > sizeof(cbits)\n");
            break;
        }
        readed = input->Read(cbits, sizeof(char), nbBytes);
        if (readed != nbBytes) {
            fprintf(stderr, "readed != nbBytes\n");
            break;
        }
        int frame_size = opus_decode(decoder, cbits, nbBytes, out, MAX_FRAME_SIZE, 0);
        if (frame_size < 0) {
            fprintf(stderr, "decoder failed: %s\n", opus_strerror(frame_size));
            break;
        }
        ++frameCount;
        output->Append((char *) out, channels * frame_size * sizeof(out[0]));
    }
    opus_decoder_destroy(decoder);
    return true;
}


void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {
    const char *end;
    const char *p;
    const char *s;
    if (path[0] && path[1] == ':') {
        if (drv) {
            *drv++ = *path++;
            *drv++ = *path++;
            *drv = '\0';
        }
    } else if (drv)
        *drv = '\0';
    for (end = path; *end && *end != ':';)
        end++;
    for (p = end; p > path && *--p != '\\' && *p != '/';)
        if (*p == '.') {
            end = p;
            break;
        }
    if (ext)
        for (s = end; (*ext = *s++);)
            ext++;
    for (p = end; p > path;)
        if (*--p == '\\' || *p == '/') {
            p++;
            break;
        }
    if (name) {
        for (s = p; s < end;)
            *name++ = *s++;
        *name = '\0';
    }
    if (dir) {
        for (s = path; s < p;)
            *dir++ = *s++;
        *dir = '\0';
    }
}

void opus2wav(const char *in_file, char *out_file) {
    FileStream input;
    FileStream output;
    input.ReadFromFile(in_file);
    Opus2Wav(&input, &output);
    stream2wav(&output, out_file);
}

void wav2opus(char *in_file, char *out_file) {
    FileStream input;
    FileStream output;
    wav2stream(in_file, &input);
    Wav2Opus(&input, &output);
    output.WriteToFile(out_file);
}

int main(int argc, char *argv[]) {
    printf("Opus Demo\n");
    printf("blog:http://tntmonks.cnblogs.com/\n");
    printf("e-mail:[email protected]\n");
    if (argc < 2)
        return -1;
    char *in_file = argv[1];
    char drive[3];
    char dir[256];
    char fname[256];
    char ext[256];
    char out_file[1024];
    splitpath(in_file, drive, dir, fname, ext);
    if (memcmp(".wav", ext, strlen(ext)) == 0) {
        sprintf(out_file, "%s%s%s.out", drive, dir, fname);
        wav2opus(in_file, out_file);
    } else if (memcmp(".out", ext, strlen(ext)) == 0) {
        sprintf(out_file, "%s%s%s_out.wav", drive, dir, fname);
        opus2wav(in_file, out_file);
    }
    printf("done.\n");
    printf("press any key to exit.\n");
    getchar();
    return 0;
}

 

項目地址:

https://github.com/cpuimage/opus

 

示例具體流程為:

1.壓縮

載入wav(拖放wav文件到可執行文件上)->壓縮->保存為out

2.解壓

載入out(拖放out文件到可執行文件上)->解壓->保存為wav

 

示例比較簡單,用cmake即可進行編譯示例代碼,詳情見CMakeLists.txt。

 

若有其他相關問題或者需求也可以郵件聯繫俺探討。

郵箱地址是: 
[email protected]


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 用for迴圈對無序數組進行排序輸出。 public class BubbleSort{ public static void main (String [] args){ int a[] = {21,9,45,17,33,72,50,12,41,39}; for (int i=a.length; - ...
  • MyBatis—Spring 項目 目前大部分的 Java 互聯網項目,都是用 Spring MVC + Spring + MyBatis 搭建平臺的。 使用 "Spring IoC" 可以有效的管理各類的 Java 資源,達到即插即拔的功能;通過 "Spring AOP" 框架,資料庫事務可以委托 ...
  • 內容:日曆對象獲取時間,設置時間,日期偏移 通過工廠化獲得對象。getInstance();get() 獲取時間信息 美國的星期是從星期天開始的,所以會有點問題set()設置方法,設置時間 //解答三月一日的前一天就是 Calendar c = Calendar.getInstance(); int ...
  • 集合的迭代器 任何集合都有迭代器。 任何集合類,都必須能以某種方式存取元素,否則這個集合容器就沒有任何意義。 迭代器,也是一種模式(也叫迭代器模式)。在java中它是一個對象,其目的是遍歷並選中其中的每個元素,而使用者(客戶端)無需知道裡面的具體細節。迭代器要足夠的“輕量”——創建迭代器的代價小。所 ...
  • Github項目地址: https://github.com/JtvDeemo/elementary arithmetic PSP ||||| |: |: |: |: | | PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘 ...
  • SocketServer其實是對socket更高級的封裝正如官網上說的:The socketserver module simplifies the task of writing network servers. 我們可以先打開以下SocketServer的源碼,看一下源碼中整體的框架 從上圖我們 ...
  • 比如:如果在微信小程式中要遍歷輸出 0-9 的數,我們會使用for迴圈 確實結果也是這樣: 但是,如果我在迴圈時同時調用wx的api介面10次,那麼輸出的結果就會不同(這是產生了閉關的效應) eg:每次調用一次wx.showToast()介面,併在成功時輸出迴圈的值。 結果: 可以看到輸出了10次1 ...
  • 數組排序 目錄 一. 冒泡排序 二. 選擇排序 三. 優化選擇排序 一. 冒泡排序 將數組元素按【從小到大排序】為例 思路:依次對臨近的兩個元素對比,將最大值放在數組最後;再將剩餘的元素對比,大值放在剩餘元素的最後. . . 以此迴圈,最後元素就是按從小到大排列。 1.1. 做之前,先瞭解這個操作: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...