Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions .github/workflows/OpusCompile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,32 +265,54 @@ jobs:
- name: Clone Repository
run: git clone https://github.com/xiph/opus.git

- name: Autogen
run: ./opus/autogen.sh

- name: Configure
working-directory: ./build
- name: Generate Build Scripts
run: |
export PATH="/opt/homebrew/bin:/opt/homebrew/opt/libtool/libexec/gnubin:$PATH"
cd ./opus
git fetch --tags --force
OPUS_VERSION="$(git describe --tags --always --match 'v*' | sed 's/^v//')"
echo "PACKAGE_VERSION=\"$OPUS_VERSION\"" > package_version
autoreconf -isf

- name: Build Static Library
run: |
cmake ../opus \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_ARCHITECTURES=${{ env.ARCH }} \
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \
-DCMAKE_OSX_SYSROOT=${{ env.SDK }} \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DOPUS_BUILD_PROGRAMS=OFF
export PATH="/opt/homebrew/bin:/opt/homebrew/opt/libtool/libexec/gnubin:$PATH"
SDKROOT="$(xcrun --sdk "${{ env.SDK }}" --show-sdk-path)"
CC="$(xcrun --sdk "${{ env.SDK }}" --find clang)"
AR="$(xcrun --sdk "${{ env.SDK }}" --find ar)"
RANLIB="$(xcrun --sdk "${{ env.SDK }}" --find ranlib)"

- name: Build
working-directory: ./build
run: cmake --build . -j $(sysctl -n hw.ncpu) --config Release
if [[ "${{ matrix.target }}" == "device" ]]; then
HOST_TRIPLE="aarch64-apple-darwin"
MIN_VERSION_FLAG="-miphoneos-version-min=13.0"
elif [[ "${{ matrix.target }}" == "simulator-arm64" ]]; then
HOST_TRIPLE="aarch64-apple-darwin"
MIN_VERSION_FLAG="-mios-simulator-version-min=13.0"
else
HOST_TRIPLE="x86_64-apple-darwin"
MIN_VERSION_FLAG="-mios-simulator-version-min=13.0"
fi

cd ./build
../opus/configure \
--host="$HOST_TRIPLE" \
--disable-shared \
--enable-static \
--disable-extra-programs \
--disable-doc \
CC="$CC" \
CFLAGS="-arch ${{ env.ARCH }} -isysroot $SDKROOT $MIN_VERSION_FLAG -O3" \
AR="$AR" \
RANLIB="$RANLIB"

make -j "$(sysctl -n hw.ncpu)" libopus.la

- name: Verify Library
working-directory: ./build
run: |
echo "=== Built library for ${{ matrix.target }} ==="
find . -name "*.a" -exec file {} \;
find . -name "*.a" -exec lipo -info {} \;
find . -path "*.libs/libopus.a" -exec file {} \;
find . -path "*.libs/libopus.a" -exec lipo -info {} \;

- name: Sanitize symbols to avoid Unity symbol conflicts
working-directory: ./build
Expand All @@ -302,9 +324,9 @@ jobs:

set -euo pipefail

echo "Collecting global defined symbols from libopus.a..."
echo "Collecting global defined symbols from .libs/libopus.a..."
# Select only defined symbols (T,D,B,S) from the archive and deduplicate
nm -g libopus.a | awk '/ [TDBS] /{print $3}' | sort -u > allsyms.txt
nm -g .libs/libopus.a | awk '/ [TDBS] /{print $3}' | sort -u > allsyms.txt

echo "Creating symbol mapping for renaming (skipping public 'opus' symbols)..."
: > mapping.txt
Expand All @@ -315,9 +337,9 @@ jobs:

if [ -s mapping.txt ]; then
echo "Applying symbol renames..."
llvm-objcopy --redefine-syms=mapping.txt libopus.a
llvm-objcopy --redefine-syms=mapping.txt .libs/libopus.a
echo "Verification:"
nm -g libopus.a | grep compute_allocation || true
nm -g .libs/libopus.a | grep compute_allocation || true
else
echo "No non-opus symbols found to rename. Skipping sanitization."
fi
Expand All @@ -327,7 +349,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ${{ env.OUTPUT_NAME }}-libopus.a
path: ./build/libopus.a
path: ./build/.libs/libopus.a

iOS-universal:
runs-on: macos-latest
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
test-core:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Run core tests
run: dotnet test ./OpusSharp.Core.Tests/OpusSharp.Core.Tests.csproj --configuration Release

smoke-apple:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Install iOS workload
run: dotnet workload install ios
- name: Pack local NuGet feed
run: ./packall.sh
- name: Run host package smoke
run: dotnet run --project ./samples/PackageSmoke/Host/OpusSharp.Smoke.Host.csproj --configuration Release
- name: Build iOS package smoke
run: dotnet build ./samples/PackageSmoke/iOS/OpusSharp.Smoke.iOS.csproj --configuration Release -f net9.0-ios -p:RuntimeIdentifier=iossimulator-arm64
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,7 @@ FodyWeavers.xsd

# Rider
.idea

# Dev machine
OpusSharp.RuntimeCheck
global.json
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<OpusSharpVersion>1.7.0</OpusSharpVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Versions need to be 1.6.1. since the official opus version is currently 1.6.1.

<RepositoryUrl>https://github.com/AvionBlock/OpusSharp</RepositoryUrl>
</PropertyGroup>
</Project>
151 changes: 151 additions & 0 deletions OpusSharp.Core.Tests/NativeLibrarySmokeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System.Runtime.InteropServices;
using OpusSharp.Core;
using Xunit;

namespace OpusSharp.Core.Tests;

public sealed class NativeLibrarySmokeTests
{
public NativeLibrarySmokeTests()
{
TestNativeLibraryBootstrapper.EnsureInitialized();
}

[Fact]
public void Version_ReturnsNonEmptyString()
{
var version = OpusInfo.Version();

Assert.False(string.IsNullOrWhiteSpace(version));
}

[Fact]
public void StringError_ReturnsKnownMessage()
{
var error = OpusInfo.StringError((int)OpusErrorCodes.OPUS_INVALID_PACKET);

Assert.False(string.IsNullOrWhiteSpace(error));
}

[Fact]
public void OpusException_PreservesMessageAndInnerException()
{
var inner = new InvalidOperationException("inner");
var exception = new OpusException("outer", inner);

Assert.Equal("outer", exception.Message);
Assert.Same(inner, exception.InnerException);
}

[Fact]
public void PublicEnumValues_MatchExpectedOpusConstants()
{
Assert.Equal(-4, (int)OpusErrorCodes.OPUS_INVALID_PACKET);
Assert.Equal(2048, (int)OpusPredefinedValues.OPUS_APPLICATION_VOIP);
Assert.Equal(4002, (int)EncoderCTL.OPUS_SET_BITRATE);
}

[Fact]
public void DefaultOpusExceptionCtor_ProducesExceptionInstance()
{
var exception = new OpusException();

Assert.NotNull(exception);
}

private static class TestNativeLibraryBootstrapper
{
private static bool _initialized;

public static void EnsureInitialized()
{
if (_initialized)
{
return;
}

var sourcePath = GetNativeLibraryPath();
var destinationPath = Path.Combine(AppContext.BaseDirectory, Path.GetFileName(sourcePath));

if (!File.Exists(destinationPath))
{
File.Copy(sourcePath, destinationPath, overwrite: true);
}

_initialized = true;
}

private static string GetNativeLibraryPath()
{
var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../"));
var runtimeFolder = GetRuntimeFolder();
var fileName = GetNativeLibraryFileName();
var path = Path.Combine(repoRoot, "OpusSharp.Natives", "runtimes", runtimeFolder, "native", fileName);

if (!File.Exists(path))
{
throw new FileNotFoundException($"Native opus library was not found at '{path}'.");
}

return path;
}

private static string GetRuntimeFolder()
{
if (OperatingSystem.IsMacOS())
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.Arm64 => "osx-arm64",
Architecture.X64 => "osx-x64",
_ => throw new PlatformNotSupportedException("Unsupported macOS architecture.")
};
}

if (OperatingSystem.IsLinux())
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.Arm => "linux-arm",
Architecture.Arm64 => "linux-arm64",
Architecture.X64 => "linux-x64",
Architecture.X86 => "linux-x86",
_ => throw new PlatformNotSupportedException("Unsupported Linux architecture.")
};
}

if (OperatingSystem.IsWindows())
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.Arm64 => "win-arm64",
Architecture.X64 => "win-x64",
Architecture.X86 => "win-x86",
_ => throw new PlatformNotSupportedException("Unsupported Windows architecture.")
};
}

throw new PlatformNotSupportedException("Unsupported host operating system.");
}

private static string GetNativeLibraryFileName()
{
if (OperatingSystem.IsWindows())
{
return "opus.dll";
}

if (OperatingSystem.IsLinux())
{
return "opus.so";
}

if (OperatingSystem.IsMacOS())
{
return "opus.dylib";
}

throw new PlatformNotSupportedException("Unsupported host operating system.");
}
}
}
19 changes: 19 additions & 0 deletions OpusSharp.Core.Tests/OpusSharp.Core.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\OpusSharp.Core\OpusSharp.Core.csproj" />
</ItemGroup>
</Project>
14 changes: 7 additions & 7 deletions OpusSharp.Core/OpusDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ public class OpusDecoder : IDisposable
/// </summary>
/// <param name="sample_rate">The sample rate, this must be one of 8000, 12000, 16000, 24000, or 48000.</param>
/// <param name="channels">Number of channels, this must be 1 or 2.</param>
/// <param name="use_static">Whether to use a statically linked version of opus.</param>
/// <param name="use_static">Set to <see langword="true"/> to force static imports, <see langword="false"/> to force dynamic imports, or <see langword="null"/> to auto-select based on platform.</param>
/// <exception cref="OpusException" />
public unsafe OpusDecoder(int sample_rate, int channels, bool use_static = false)
public unsafe OpusDecoder(int sample_rate, int channels, bool? use_static = null)
{
_useStatic = use_static;
_useStatic = OpusRuntime.ShouldUseStaticImports(use_static);
var error = 0;
_handler = use_static
_handler = _useStatic
? StaticNativeOpus.opus_decoder_create(sample_rate, channels, &error)
: NativeOpus.opus_decoder_create(sample_rate, channels, &error);
CheckError(error);
Expand Down Expand Up @@ -158,7 +158,7 @@ public unsafe int Decode(Span<byte> input, int length, Span<float> output, int f
}
}
#endif

/// <summary>
/// Decodes an opus encoded frame.
/// </summary>
Expand Down Expand Up @@ -214,7 +214,7 @@ public unsafe int Decode(byte[]? input, int length, short[] output, int frame_si
return result;
}
}

/// <summary>
/// Decodes an opus encoded frame.
/// </summary>
Expand Down Expand Up @@ -430,4 +430,4 @@ protected static void CheckError(int error)
throw new OpusException(((OpusErrorCodes)error).ToString());
}
}
}
}
Loading