2014년 7월 22일 화요일

Renewal FFmpeg official site



Link1. ffmpeg.org

FFmpeg 사이트가 리뉴얼 되었다.
완전 내 취향으로 변했다 하앍하앍 //ㅛ//

2014년 7월 7일 월요일

How to build zlib on Visual Studio(비쥬얼스튜디오에서 사용 가능한 zlib.lib 빌드하기)


거두절미하고 핵심!!
  1. Download zlib-1.2.8.tar.gz
  2. Uncompress zlib-1.2.8.tar.gz
  3. Execute [Visual Studio command prompt]
  4. cd Uncompressed directory
  5. nmake /f win32/Makefile.msc
  6. Check output files. {"zlib.lib", "zlib1.dll", "zdll.lib"}
  7. Use [zlib.lib]
서버-클라이언트 솔루션을 만들다 보면 전송 데이터의 압축을 위해 gzip을 이용하는 경우가 생각보다 상당히 많다. 그리고 이 gzip 알고리즘을 구현하기 위해 zlib을 이용한 오픈소스 프로젝트들과 예제들이 굉장히 많다. 헌데 이 zlib은 기본적으로 리눅스 환경이 기본 베이스라 무턱대고 비주얼스튜디오 프로젝트에 포함시켜 빌드하려고 하면 애로사항이 꽃핀다. 때문에 공식 홈페이지의 zlib128-dll.zip 파일을 다운로드 받아 shared(dll) 방식으로 많이들 사용한다. 하지만 shared가 아니라 static(lib) 라이브러리가 필요한 경우도 많다. 그럴 경우 위 순서대로 빌드 후 static library를 사용하면 된다.

2014년 6월 18일 수요일

YouTube 영상을 저장해보자

개인적인 수집 목적이든 필요한 자료의 획득을 위해서든 최근 VOD 제공 업체의 동영상을 저장하는 일이 많아졌다. 필자의 경우 업무때문에 여러 종류의 뮤직 비디오나 짧은 영상들을 최대한 많이 확보해 트랜스코딩 및 필터링 샘플로 사용하는게 좋기 때문에 더욱 그렇다.
하지만 VOD 제공 업체에서 자신들의 밥줄을 그냥 다운로드하게 해줄 이유가 없기 때문에 어떻게든 동영상들을 저장할 방법을 찾아야 했다. 그렇게 방법을 찾다가 약 3년 전부터 애용하기 시작한 툴이 있는데 바로 keepvid.com이다. 이 툴은 JAVA 기반에서 동작하며 YouTube에 있는 영상을 저장할 수 있도록 도와준다. 사용법은 아래와 같다.


  1. JAVA 설치(이미 설치되어 있다면 넘어가자)
  2. keepvid.com 접속
  3. YouTube에서 저장하고자 하는 영상 선택
  4. 선택한 영상의 웹 주소 복사
  5. keepvid.com 내 [웹 주소 입력 텍스트 박스]에 주소 입력 후 우측 [DOWNLOAD 버튼] 클릭
  6. 나열되는 목록 중 자신이 원하는 포맷과 해상도의 영상을 [다른 이른으로 저장]
    (3GP, FLV, MP4, M4A, WEBM 지원)

근데 문제가 있다. MP4 포맷 720p 이하 해상도의 경우 영상과 음성이 먹싱된 동영상으로 지원하지만 그 외 1080p 해상도 MP4와 WEBM의 경우 영상과 음성을 따로 제공한다. 아마도 유튜브의 스트리밍 방식 때문인걸로 추측된다. 그럼 울며 겨자먹기로 영상만 보아야 하는 것인가? 그건 아니다 다 방법이 있다. 먹싱되지 않은 두 영상과 음성이 확보만 된다면 두 소스를 먹싱하기만 하면 된다. 다행히도 keepvid.com은 M4A로 음성 소스를 따로 제공하고 있다. 아래는 이 1080p MP4와 M4A를 이용해 두 소스를 하나의 동영상으로 먹싱하는 방법이다. 

  1. ffmpeg 소스를 받아 빌드한다.(윈도 사용자의 경우 Zeranoe FFmpeg Builds에서 자신에게 적합한 바이너리를 다운로드한다)
  2. 다운로드한 두 소스를 ffmpeg을 통해 아래와 같은 형식으로 먹싱한다.
    ffmpeg -i $VID_SRC -i $AUD_SRC -c:v copy -c:a copy $DST_PATH
    • $VID_SRC: 영상 소스 경로
    • $AUD_SRC: 음성 소스 경로
    • $DST_PATH: 먹싱된 동영상을 생성할 경로
  3. 감상(하앍//ㅅ//)

ffmpeg을 실행하면서 쓰인 저 스크립트에 대한 설명을 하다보면 이 포스트가 원 의도에서 엄청나게 벗어날 것 같으니 일단은 저렇게 쓴다는 정도로만 이해하도록 하자. 추후 ffmpeg에 대해 다루게 된다면 자세하게 다루어 보겠다(근데 귀찮아...)

2014년 6월 15일 일요일

현존하는 최강의 가성비를 자랑하는 책상 [소프시스] - 위더스 컴퓨터 테이블

처음으로 쓰는 지름신 포스트다. ㅋㅋㅋ

이번 포스트의 제품은 [소프시스] - 위더스 컴퓨터 테이블 (그냥 책상이라 하면 입에 가시가 돋는건가?=_=a;)


거두절미하고 바로 평점 들어가보자.



  • 디자인: ★★★★☆
    • 워낙에 심플한 걸 좋아하는 필자로써 더할나위 없이 깔끔하고 훌륭한 디자인이었다. 하지만 마감 부분에서 언급하겠지만 프레임에 숭숭 뚫린 구멍이 좀...
  • 마감: ★★☆☆☆
    • 우선 자재는 쌈마이 합판이다. 그래서 겉을 나무질감의 경화 시트지를 사용했다. 뭐 여기까지는 좋은데 그 시트지 마감이 조금 아쉽다. 
    • 프레임의 굵기가 생각보다 얇다. 때문에 조립 시 드라이버에 조금만 힘을 줘서 조으다 보면 프레임이 살짝 함몰되는 현상을 볼 수 있다. 하지만 그렇게 심각한 수준은 아니다.
    • 책장 프레임을 좌측이나 우측으로 자유롭게 배치할 수 있도록 프레임에 많은 천공이 있는데 이게 생각보다 신경쓰인다. 값싼 플라스틱 버튼으로 이런 천공들을 메꾸는 방법을 썼더라면 훨씬 완성도가 높았을 것 같다.
  • 배송: ★★★☆☆
    • 그냥 뭐 똑같다. 월요일에 주문했고 수요일에 수령하였다.
  • 가격: ★★★★★ +α
    • 이 가격대에 조그마한 협탁도 못 살걸? ㅋㅋㅋ
  • 내구도: ★★★☆☆
    • 프레임이 생각보다 얇아 일부러 각 프레임들을 반대 방향으로 비틀었을 시 조금씩 흔들리는 것을 확인할 수 있다. 하지만 그렇게 문제가 있는 수준은 아니다.
  • 종합: ★★★★★
    • 가성비 최강!! 내 기준에서 별 다섯개면 진짜 최고인거다. 왠만하면 별 3개 이상 안주는 성격인데 말이다.

그럼 실제 조립 및 사용하고 있는 사진을 보도록 하자.
굉장히 심플하다. 아이 좋아 ㅋㅋ

2014년 5월 12일 월요일

2014년 5월 8일 목요일

libavformat을 이용해 영상의 길이와 프레임레이트를 구해보자 ( How to get the duration and frame rate using libavformat )

거두절미하고 핵심!!

int64_t AVFormatContext::duration
AVRational AVStream::avg_frame_rate
static double av_q2d ( AVRational a )

위에 적힌 저 세개만 알면 끝난다. 뭐 자세한 내용을 나중에 생각하고 아래 소스코드를 보도록 하자.


  1 ////////////////////////////////////////////////////////////////////////////////
  2 //
  3 // File name  : ffinfo.c
  4 // Last edit  : 2014-05-08 crazybird
  5 // Dependency : ffmpeg 2.1.x (libavformat.55.x.x)
  6 //
  7
  8
  9 #include <stdio.h>
 10 #include <stdint.h>
 11 #include <libavformat/avformat.h>
 12
 13
 14 int main(int argc, char** argv)
 15 {
 16     AVFormatContext *fmt_ctx = NULL;    // Format context
 17     int ret = 0;                        // Return value
 18     AVStream *st = NULL;                // Stream
 19     double fps = 0.0;                   // Video fps
 20     int64_t dur = 0;                    // General duration
 21     unsigned int nb_streams = 0;        // A list of all streams in the file
 22     unsigned int idx = 0;               // for(;;) iterator
 23
 24     // Verify arguments
 25     if (argc != 2)
 26     {
 27         printf(
 28             "usage: %s <input_file>\n"
 29             "example program to demonstrate the use of the "
 30             "libavformat metadata API.\n\n"
 31             , argv[0]);
 32
 33         return 1;
 34     }
 35
 36     av_register_all();  // Register URL Protocols.
 37
 38     // Open media file
 39     ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL);
 40     if (ret != 0)
 41     {
 42         return ret;
 43     }
 44
 45     // Find video stream index
 46     nb_streams = fmt_ctx->nb_streams;
 47     if (nb_streams > 0)
 48     {
 49         for (idx = 0; idx < nb_streams; idx++)
 50         {
 51             st = fmt_ctx->streams[idx];
 52             if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
 53             {
 54                 break;
 55             }
 56
 57             st = NULL;
 58         }
 59     }
 60
 61     // Is invalid stream?
 62     if (st == NULL)
 63     {
 64         printf("%s does not exist in the video stream.", argv[1]);
 65
 66         goto END;
 67     }
 68
 69     // IMPORTANT : Get general duration and video frame rate
 70     dur = fmt_ctx->duration;
 71     fps = av_q2d(st->avg_frame_rate);
 72
 73     // Output
 74     printf(
 75         "video fps=%f\n"
 76         "general duration=%lld\n"
 77         , fps, dur);
 78
 79 END:
 80     avformat_close_input(&fmt_ctx);
 81
 82     return 0;
 83 }

 84


보는 것과 같이 매우 간단하다. 더 자세한 내용은 추후에 보충하도록 하겠다.

2014년 4월 15일 화요일

로그를 좀더 이쁘게 박아보자(g++ 기준)

로그를 작성할 때 그냥 막 적어도 상관은 없다만 이왕이면 해당 파일명과 소스라인 위치 함수명까지 알면 더욱 좋을것이다. 언제나 그렇듯이 긴말 말고 아래 매크로 또는 클래스들을 보자.
  • __FILE__ : 소스코드가 포함된 파일 이름을 반환
  • __LINE__ : 소스코드의 줄 번호를 반환
  • __func__ : 소스코드가 포함된 함수 이름을 반환
  • __PRETTY_FUNCTION__ : 소스코드가 포함된 클래스 이름과 함수 이름을 '이쁘게' 반환
  • typeid(T t).name() : 맹글링된 클래스 이름이나 타입 이름을 반환
다시 말하지만 위 매크로들은 g++ 기준이다. MSVCclang에서는 이와 같거나 다른 이름을 가지지만 같은 역할을 하는 매크로가 있을 것이다. 아래는 위 매크로를 이용한 간단한 예제이다.


  1 #include <iostream>
  2 #include <typeinfo>
  3
  4
  5 class CPrettyLog
  6 {
  7 public:
  8     void Print(void)
  9     {
 10         std::cout
 11             << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 12             << "__func__ = " << __func__ << std::endl
 13             << "__LINE__ = " << __LINE__ << std::endl
 14             << "__FILE__ = " << __FILE__ << std::endl
 15             << "typeid(this).name() = " << typeid(this).name() << std::endl
 16             << std::endl;
 17     }
 18 };
 19
 20
 21 void Print(void)
 22 {
 23     std::cout
 24         << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 25         << "__func__ = " << __func__ << std::endl
 26         << "__LINE__ = " << __LINE__ << std::endl
 27         << "__FILE__ = " << __FILE__ << std::endl
 28         << std::endl;
 29 }
 30
 31
 32 int main(int argc, char** argv)
 33 {
 34     CPrettyLog pl;
 35     pl.Print();
 36
 37     Print();
 38
 39     std::cout
 40         << "__PRETTY_FUNCTION__ = " << __PRETTY_FUNCTION__ << std::endl
 41         << "__func__ = " << __func__ << std::endl
 42         << "__LINE__ = " << __LINE__ << std::endl
 43         << "__FILE__ = " << __FILE__ << std::endl
 44         << std::endl;
 45
 46     return 0;
 47 }
 48

위 예제를 컴파일 후 실행하면 아래와 같은 결과를 얻을 수 있다.


__PRETTY_FUNCTION__ = void CPrettyLog::Print()
__func__ = Print
__LINE__ = 13
__FILE__ = main.cpp
typeid(this).name() = P10CPrettyLog

__PRETTY_FUNCTION__ = void Print()
__func__ = Print
__LINE__ = 26
__FILE__ = main.cpp

__PRETTY_FUNCTION__ = int main(int, char**)
__func__ = main
__LINE__ = 42
__FILE__ = main.cpp


2014년 4월 8일 화요일

리눅스의 디렉토리 구조와 역할

리눅스의 각 디렉토리 구조는 커널버전과 제품군에 따라 조금씩 다르지만 기본적인 맥락은 거의 비슷하다. 개인적으로 장문의 글로 장황하게 설명하는 걸 싫어하니 아래 트리와 주석을 보도록 하자.

~           
├── bin/    
├── lib/    
├── include/
├── src/    
├── etc/    
├── var/    
└── tmp/    

  • ~
    • 유저의 홈 디렉토리
    • 유저 디렉토리의 모든 시작지점
  • bin/
    • 실행 바이너리 및 쉘 스크립트들이 있음
    • binary file, .sh, .java
  • lib/
    • 라이브러리 파일들이 있음
    • .a, .so, .jar
  • include/
    • 헤더 및 전처리 파일들이 있음
    • 주로 lib/에서 필요한 헤더들이 있음
    • .h, .hh, .hpp, .py
  • src/
    • 작업중이거나 빌드 전의 소스코드들이 있음
  • etc/ 
    • 설정값이나 정보와 같이 고정적인 값들을 저장한 파일들이 있음
    • 주로 바이너리 이름 뒤에 확장자를 붙여 사용함
    • .conf, .nfo, .info
  • var/ 
    • 로그나 상태값과 같은 수시로 변하는 의미있는 정보들을 저장한 파일들이 있음
    • 주로 바이너리 이름을 가지는 디렉토리 내에 저장됨
    • .log, .stat, .queue, .pool
  • tmp/ 
    • 작업 중 임시로 사용되거나 수시로 생성/삭제되는 파일들이 있음
    • 주로 바이너리 이름을 가지는 디렉토리 내에 저장됨
    • .dummy, .buffer, 작업중인 파일 등

물론 모든 리눅스 시스템과 프로젝트들이 위 규칙을 따르는 건 아니지만 적어도 몇십년동안 많은 개발자와 관리자들에게 사랑을 받아온 구조에는 그만한 이유가 있다. 필자 또한 처음에는 이런저런 구조를 도입해보려 했었지만 결국 종착역은 위 구조였다. 그 이유는 아래와 같다.

  • 한 솔루션 내 여러 프로젝트 사이에서 일어나는 사이드 이펙트를 빠르고 직접적(?)으로 파악할 수 있음(이 얘기에 대해서는 추후 리눅스의 이상적인 솔루션(프로젝트) 구조에서 다루도록 하겠음)
  • 프로젝트의 유지보수 및 패치작업이 수월함
  • 관리자와 개발자 간 의사소통이 원활해짐

일단 이번 포스트는 이쯤에서 마무리 지으려고 한다. 쓰다보면 끝이 없을 것 같다.
이번 포스트의 핵심은 '몇십년동안 사랑을 받아온 구조에는 그만한 이유가 있다'



blogDevCrazyBird.Start();

다시 블로그를 운영하기로 마음 먹었다.
이유는 일반 SNS로는 다 표현하기 힘든 일기성 글이라던가 각종 정보성 기사들의 스크랩 및 개인적인 개발 공부를 위해서다. 앞으로 리눅스 및 C++(특히 C++11/14) 위주의 포스트가 주를 이룰 듯 하다. 매주 화/수/목 진행하는 아프리카 방송 다시보기도 업로드할 예정이다.
부디 이번에는 귀차니즘과 게임에 빠져서 유령 블로그가 되지는 않았으면 한다.