맥에서 동적 라이브러리 참조문제 (Reason: image not found)

ffmpeg를 사용해볼 겸 테스트용 프로젝트를 생성하여 아래와같이 구성.

빌드까지 문제가 없었으나 실행시키면 “Reason: image not found“라는 에러가 발생.

뜬금없이 libswresample.3.dylib를 찾을 수 없다는 에러가 발생한다. 구글링 해본 결과 otool과 install_name_tool을 사용하여 해결해야하는것으로 결론.

실행파일과 dylib파일을 otool -L 명령으로 확인해보면 라이브러리 위치를 ffmpeg를 빌드할 때 prefix 대상 위치로 참조하도록 되어있다. libswresample.3.5.100.dylib 파일을 확인하면 나타나는 항목 중 첫번째 항목은 LC_ID_DYLIB, 두번째 부터 나타나는 항목은 LC_LOAD_DYLIB으로 차이가 있다. (직접 확인하려면 otool -L 대신 otool -l 옵션으로 실행하면 확인가능) 이중 LC_ID_DYLIB는 바이너리가 생성될 때 라이브러리가 참조되는 경로로 링크가되는것으로 보인다. 이 경로는 install_name_tool -id “라이브러리 경로” “라이브러리 파일”로 지정 가능하고 LC_LOAD_DYLIB는 바이너리에서 의존하는 라이브러리며 install_name_tool -change “기존경로” “새로운경로” “라이브러리 파일” 명령으로 변경 가능하다. (경로는 상대경로를 위한 예약어 비슷한 키워드가 있다. @executable_path, @loader_path, @rpath이다. 각각 의미는 구글링으로 패스.)

install_name_tool -id @executable_path/../Frameworks/libavcodec.58.54.100.dylib libavcodec.58.54.100.dylib
install_name_tool -change /Users/iruis/OpenSource/ffmpeg-4.2.1-lgpl/output/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.5.100.dylib libavcodec.58.54.100.dylib

위와같은 명령으로 라이브러리 경로를 하나하나 바꾸고나면 맥의 패키지 내 복사되는 라이브러리가 참조되어 정상 로딩되어 실행된다. 덕분에 거의 반나절을 삽질한건 안비밀.

Objective-C와 Swift에서 NSData(Data) 데이터 단순 탐색

Objective-C 코드 (bytes는 NSData 클래스 객체에서 bytes를 호출하여 얻은 데이터 포인터)

for(uint32_t i = 0; i < dataSize - 4; i++)
{
    uint32_t startCode = *(uint32_t *)(bytes + i);

    if(startCode == 0x01000000)
    {
        [points addObject:[NSValue valueWithPointer:bytes + i]];
        [nalSizes addObject:[NSNumber numberWithInt:4]];
        
        i += 3;
    }
    
    if((startCode & 0x00FFFFFF) == 0x00010000)
    {
        [points addObject:[NSValue valueWithPointer:bytes + i]];
        [nalSizes addObject:[NSNumber numberWithInt:3]];
        
        i += 2;
    }
}

 

Swift 코드 (3.0으로 빌드, streamData는 Data 클래스의 객체)

streamData.withUnsafeBytes { (p8: UnsafePointer) in
    while i < count
    {
        if p8.advanced(by: i + 0).pointee == 0 && p8.advanced(by: i + 1).pointee == 0
        {
            if p8.advanced(by: i + 2).pointee == 0 && p8.advanced(by: i + 3).pointee == 1
            {
                offsets.append(i)
                nalSizes.append(4)
                
                i += 4
                
                continue
            }
            if p8.advanced(by: i + 2).pointee == 1
            {
                offsets.append(i)
                nalSizes.append(3)
                
                i += 3
                
                continue
            }
        }
        i += 1
    }
}

 

H264에서 NAL 헤더를 찾는 코드이다. 어쩌다보니 기존에 Objective-C로 코드를 만들어놓은걸 Swift로 옮기다보니... 생각보다 삽질을 많이하게되었다. 프레임워크는 동일하지만 Swift는 기본적으로 포인터 연산을 지원하지않기때문에 C에서의 구조체, 바이너리 데이터를 int형식으로 형변환하여 비교하는 등의 연산에서 자유롭지않아보인다. 동일한 LLVM에서 컴파일되고 1:1 호환 가능하다고는하지만 위와같은 사항 외에도 언어의 특성이있어서 목적은 동일하지만 결국은 코드의 구조는 달라졌다.

딱봐도 Swift의 코드가 비효율적으로 보이지만 초당 1메가정도의 데이터에서 CPU점유율을 보면 1~2%정도만 더 Swift에서 많이 사용한다. 아직 릴리즈 빌드는 찾아본적없어서 export하게되면 또 차이날지는 모르겠지만... 현재로서는 약 2~3시간 삽질을 한 결과 위 코드가 Objective-C의 코드와 가장 근접한 CPU 사용률을 보이는 코드이다.

아래는 순서대로 Objective-C, Swift 코드에서의 CPU 사용률

VideoToolbox 디코더에서 픽셀 포맷을 BGRA와 420YpCbCr를 사용할때의 CPU 점유율 차이

일단 각각의 차이

kCVPixelFormatType_32BGRA 값을 사용할 때

kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 값을 사용할 때

하드웨어 스펙

420YpCbCr 색상으로 출력할 때 평균적으로 3~4%정도 점유율이 더 적다. Metal 텍스쳐 두개를 생성하고 GPU에서 색상변환하기위해 소비되는 연산보다 VideoToolbox에서 BGRA로 변환하여 CVImageBufferRef 객체를 생성하는 부하가 더 큰것으로 보인다.

맥에서 어느정도 끝나면 iOS앱에도 Metal을 사용해서 얼마나 차이나는지 비교해봐야겠다.

Video Decode Acceleration Framework 사용 시 주의점

최근 거의 2주일동안 apple의 하드웨어 H264 디코더인 VDA와 씨름을 하였다. 이건 H264의 깊이있는 지식을 가지고 쓴것이 아니고 단지 VDA로 하드웨어 디코딩을 하기위해 중점을 맞춘것이니 깊이있는 지식은 넘어가고 VDA나 VideoToolbox를 사용하기위해 참고하는 정도만으로 넘어가야 할 것이다.

VDA에 관한 기술문서로 https://developer.apple.com/library/content/technotes/tn2267/_index.html 페이지가 거의 유일하며, 그나마 참조할건 http://stackoverflow.com/questions/29525000/how-to-use-videotoolbox-to-decompress-h-264-video-stream 이 글의 댓글이 거의 유일하다. (물론 VideoToolbox와 VDA는 초기화 방법이 서로 다르지만, VDA에서 지원되는 기능은 VideoToolbox에서도 동일하게 지원하는거로 보이고 avcC라는 것을 생성하지 않아도 되기때문에 초기화가 더 수월하다.)

먼저 이론적인 기본은 위 stackoverflow의 댓글로 확인하면 될것이다. 약간 첨언한다면 H264는 3바이트, 또는 4바이트짜리 Start Code라고 불리는 코드로 각각의 프레임을 구분한다. 그리고 디코딩에 필요한 NALU 타입은 7 (SPS), 8 (PPS), 5 (IDR), 1 (non-IDR) 정도로 보인다.

디코더를 초기화하기 위해서는 SPS, PPS가 필요하며, 이 정보를 바탕으로 디코딩을 한다. (영어가 된다면 https://developer.apple.com/videos/play/wwdc2014/703/ 이것이 참고가 될것이고, 프레젠테이션 자료는 Resource 탭에서 받을 수 있다.) Continue reading Video Decode Acceleration Framework 사용 시 주의점