UWP平台Taglib编译

猪小花1号2018-12-04 10:36

此文已由作者郑博授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验


最近开始开发UWP平台的App,项目需要用到Taglib进行音视频文件的标签信息读写,Google并没有现成的Binaries可以使用;下面记录下自己编译的全过程。


1.工具准备:
a)Taglib源码:https://taglib.github.io/releases/taglib-1.10.tar.gz (这里选用1.10版本,其他版本可以在https://taglib.github.io上下载)
b)Zlib源码:https://github.com/madler/zlib/archive/v1.2.8.zip (这里选用1.2.8版本,其他版本可以在https://github.com/madler/zlib/releases上下载)
c)CMake工具:https://cmake.org/files/v3.4/cmake-3.4.1-win32-x86.exe (这里选用3.4.1版本,因为3.4.0以后的版本才支持VS2015)

工具安装和源码解压过程不再赘述。

2.Zlib编译:
a)点击“Browse Source...”选择zlib源码的存储目录,点击“Browse Build...”选择工程目录(这里不建议跟源码放在一个目录中,因为UWP要3个平台)
b)点击“Configure”这是会弹出一个选择compiler的弹框,这里简单说明一下,X86选Visual Studio 14 2015,X64选Visual Studio 14 2015 Win64,ARM选Visual Studio 14 2015 ARM,3个平台均勾选Use default native compilers即可,选Finish进行确认
c)修改CMAKE_C_FLAGS,最末尾添加/DWINAPI_FAMILY=WINAPI_FAMILY_APP /D_WIN32_WINNT=0x0A00
d)修改CMAKE_C_STANDARD_LIBRARIES,清空全部字段,填写WindowsApp.lib
e)修改各种INSTALL路径,改成自己的目标目录即可,不再赘述
f)点击Generate,在工程目录里打开zlib.sln,右键INSTALL工程,Build完Zlib就编译完成了,3个平台方法一致

3.Taglib编译:
a)同zlib
b)同zlib
c)修改CMAKE_C_FLAGS & CMAKE_CXX_FLAGS,最末尾添加/D_UNICODE /DUNICODE /DWINAPI_FAMILY=WINAPI_FAMILY_APP /D_WIN32_WINNT=0x0A00
d)修改CMAKE_C_STANDARD_LIBRARIES & CMAKE_CXX_STANDARD_LIBRARIES,清空全部字段,填写WindowsApp.lib
e)勾选ENABLE_STATIC
f)配置ZLIB_INCLUDE_DIR/ZLIB_LIBRARY_DEBUG/ZLIB_LIBRARY_RELEASE
g)修改各种INSTALL路径,改成自己的目标目录即可,不再赘述
h)点击Generate生成工程文件

因为Taglib在文件操作时使用了CreateFileW,但是这些接口UWP上是禁止使用的,我们要修改taglib\toolkit\tfilestream.cpp,完整修改代码如下:
/***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/

/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/

#include "tfilestream.h"
#include "tstring.h"
#include "tdebug.h"

#ifdef _WIN32
# include
#else
# include
# include
#endif

#ifdef _WIN32
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
# include
/*
convert string to wstring
*/
std::wstring c2w(const char *data)
{
int size = MultiByteToWideChar(CP_UTF8, 0, data, -1, nullptr, 0);
if (size > 0) {
auto wstr = std::make_unique(size);

if (MultiByteToWideChar(CP_UTF8, 0, data, -1, wstr.get(), size) > 0) {
return std::wstring(wstr.get());
}
}
return std::wstring(L"");
}
#endif
#endif

using namespace TagLib;

namespace
{
#ifdef _WIN32

// Uses Win32 native API instead of POSIX API to reduce the resource consumption.

typedef FileName FileNameHandle;
typedef HANDLE FileHandle;

const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE;

inline FileHandle openFile(const FileName &path, bool readOnly)
{
const DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE);

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
const wchar_t *fp = NULL;

std::wstring wstr = path.wstr();

if (!wstr.empty())
fp = wstr.c_str();
else if (!path.str().empty()) {
wstr = c2w(path.str().c_str());
if (!wstr.empty())
fp = wstr.c_str();
}

if (fp)
return CreateFile2(fp, access, FILE_SHARE_READ, OPEN_EXISTING, NULL);
#else
if(!path.wstr().empty())
return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
else if(!path.str().empty())
return CreateFileA(path.str().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
#endif
else
return InvalidFileHandle;
}

inline void closeFile(FileHandle file)
{
CloseHandle(file);
}

inline size_t readFile(FileHandle file, ByteVector &buffer)
{
DWORD length;
if(ReadFile(file, buffer.data(), static_cast(buffer.size()), &length, NULL))
return static_cast(length);
else
return 0;
}

inline size_t writeFile(FileHandle file, const ByteVector &buffer)
{
DWORD length;
if(WriteFile(file, buffer.data(), static_cast(buffer.size()), &length, NULL))
return static_cast(length);
else
return 0;
}

#else // _WIN32

struct FileNameHandle : public std::string
{
FileNameHandle(FileName name) : std::string(name) {}
operator FileName () const { return c_str(); }
};

typedef FILE* FileHandle;

const FileHandle InvalidFileHandle = 0;

inline FileHandle openFile(const FileName &path, bool readOnly)
{
return fopen(path, readOnly ? "rb" : "rb+");
}

inline void closeFile(FileHandle file)
{
fclose(file);
}

inline size_t readFile(FileHandle file, ByteVector &buffer)
{
return fread(buffer.data(), sizeof(char), buffer.size(), file);
}

inline size_t writeFile(FileHandle file, const ByteVector &buffer)
{
return fwrite(buffer.data(), sizeof(char), buffer.size(), file);
}

#endif // _WIN32
}

class FileStream::FileStreamPrivate
{
public:
FileStreamPrivate(const FileName &fileName)
: file(InvalidFileHandle)
, name(fileName)
, readOnly(true)
{
}

FileHandle file;
FileNameHandle name;
bool readOnly;
};

////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

FileStream::FileStream(FileName fileName, bool openReadOnly)
: d(new FileStreamPrivate(fileName))
{
// First try with read / write mode, if that fails, fall back to read only.

if(!openReadOnly)
d->file = openFile(fileName, false);

if(d->file != InvalidFileHandle)
d->readOnly = false;
else
d->file = openFile(fileName, true);

if(d->file == InvalidFileHandle)
{
# ifdef _WIN32
debug("Could not open file " + fileName.toString());
# else
debug("Could not open file " + String(static_cast(d->name)));
# endif
}
}

FileStream::~FileStream()
{
if(isOpen())
closeFile(d->file);

delete d;
}

FileName FileStream::name() const
{
return d->name;
}

ByteVector FileStream::readBlock(ulong length)
{
if(!isOpen()) {
debug("FileStream::readBlock() -- invalid file.");
return ByteVector::null;
}

if(length == 0)
return ByteVector::null;

const ulong streamLength = static_cast(FileStream::length());
if(length > bufferSize() && length > streamLength)
length = streamLength;

ByteVector buffer(static_cast(length));

const size_t count = readFile(d->file, buffer);
buffer.resize(static_cast(count));

return buffer;
}

void FileStream::writeBlock(const ByteVector &data)
{
if(!isOpen()) {
debug("FileStream::writeBlock() -- invalid file.");
return;
}

if(readOnly()) {
debug("FileStream::writeBlock() -- read only file.");
return;
}

writeFile(d->file, data);
}

void FileStream::insert(const ByteVector &data, ulong start, ulong replace)
{
if(!isOpen()) {
debug("FileStream::insert() -- invalid file.");
return;
}

if(readOnly()) {
debug("FileStream::insert() -- read only file.");
return;
}

if(data.size() == replace) {
seek(start);
writeBlock(data);
return;
}
else if(data.size() < replace) {
seek(start);
writeBlock(data);
removeBlock(start + data.size(), replace - data.size());
return;
}

// Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
// and avoid TagLib's high level API for rendering just copying parts of
// the file that don't contain tag data.
//
// Now I'll explain the steps in this ugliness:

// First, make sure that we're working with a buffer that is longer than
// the *differnce* in the tag sizes. We want to avoid overwriting parts
// that aren't yet in memory, so this is necessary.

ulong bufferLength = bufferSize();

while(data.size() - replace > bufferLength)
bufferLength += bufferSize();

// Set where to start the reading and writing.

long readPosition = start + replace;
long writePosition = start;

ByteVector buffer = data;
ByteVector aboutToOverwrite(static_cast(bufferLength));

while(true)
{
// Seek to the current read position and read the data that we're about
// to overwrite. Appropriately increment the readPosition.

seek(readPosition);
const size_t bytesRead = readFile(d->file, aboutToOverwrite);
aboutToOverwrite.resize(bytesRead);
readPosition += bufferLength;

// Check to see if we just read the last block. We need to call clear()
// if we did so that the last write succeeds.

if(bytesRead < bufferLength)
clear();

// Seek to the write position and write our buffer. Increment the
// writePosition.

seek(writePosition);
writeBlock(buffer);

// We hit the end of the file.

if(bytesRead == 0)
break;

writePosition += buffer.size();

// Make the current buffer the data that we read in the beginning.

buffer = aboutToOverwrite;
}
}

void FileStream::removeBlock(ulong start, ulong length)
{
if(!isOpen()) {
debug("FileStream::removeBlock() -- invalid file.");
return;
}

ulong bufferLength = bufferSize();

long readPosition = start + length;
long writePosition = start;

ByteVector buffer(static_cast(bufferLength));

for(size_t bytesRead = -1; bytesRead != 0;)
{
seek(readPosition);
bytesRead = readFile(d->file, buffer);
readPosition += bytesRead;

// Check to see if we just read the last block. We need to call clear()
// if we did so that the last write succeeds.

if(bytesRead < buffer.size()) {
clear();
buffer.resize(bytesRead);
}

seek(writePosition);
writeFile(d->file, buffer);

writePosition += bytesRead;
}

truncate(writePosition);
}

bool FileStream::readOnly() const
{
return d->readOnly;
}

bool FileStream::isOpen() const
{
return (d->file != InvalidFileHandle);
}

void FileStream::seek(long offset, Position p)
{
if(!isOpen()) {
debug("FileStream::seek() -- invalid file.");
return;
}

#ifdef _WIN32

DWORD whence;
switch(p) {
case Beginning:
whence = FILE_BEGIN;
break;
case Current:
whence = FILE_CURRENT;
break;
case End:
whence = FILE_END;
break;
default:
debug("FileStream::seek() -- Invalid Position value.");
return;
}

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
LARGE_INTEGER distance = { 0 };
distance.QuadPart = offset;

if (!SetFilePointerEx(d->file, distance, NULL, whence))
#else
SetLastError(NO_ERROR);
SetFilePointer(d->file, offset, NULL, whence);

const int lastError = GetLastError();
if(lastError != NO_ERROR && lastError != ERROR_NEGATIVE_SEEK)
#endif
debug("FileStream::seek() -- Failed to set the file pointer.");

#else

int whence;
switch(p) {
case Beginning:
whence = SEEK_SET;
break;
case Current:
whence = SEEK_CUR;
break;
case End:
whence = SEEK_END;
break;
default:
debug("FileStream::seek() -- Invalid Position value.");
return;
}

fseek(d->file, offset, whence);

#endif
}

void FileStream::clear()
{
#ifdef _WIN32

// NOP

#else

clearerr(d->file);

#endif
}

long FileStream::tell() const
{
#ifdef _WIN32

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
LARGE_INTEGER distance = { 0 };
LARGE_INTEGER position = { 0 };
if (SetFilePointerEx(d->file, distance, &position, FILE_CURRENT)) {
return static_cast(position.QuadPart);
}
#else
SetLastError(NO_ERROR);
const DWORD position = SetFilePointer(d->file, 0, NULL, FILE_CURRENT);
if(GetLastError() == NO_ERROR) {
return static_cast(position);
}
#endif
else {
debug("FileStream::tell() -- Failed to get the file pointer.");
return 0;
}

#else

return ftell(d->file);

#endif
}

long FileStream::length()
{
if(!isOpen()) {
debug("FileStream::length() -- invalid file.");
return 0;
}

#ifdef _WIN32

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
LARGE_INTEGER fileSize = { 0 };

if (GetFileSizeEx(d->file, &fileSize)) {
return static_cast(fileSize.QuadPart);
}
#else
SetLastError(NO_ERROR);
const DWORD fileSize = GetFileSize(d->file, NULL);

if (GetLastError() == NO_ERROR) {
return static_cast(fileSize);
}
#endif
else {
debug("FileStream::length() -- Failed to get the file size.");
return 0;
}

#else

const long curpos = tell();

seek(0, End);
const long endpos = tell();

seek(curpos, Beginning);

return endpos;

#endif
}

////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

void FileStream::truncate(long length)
{
#ifdef _WIN32

const long currentPos = tell();

seek(length);

SetLastError(NO_ERROR);
SetEndOfFile(d->file);
if(GetLastError() != NO_ERROR) {
debug("FileStream::truncate() -- Failed to truncate the file.");
}

seek(currentPos);

#else

const int error = ftruncate(fileno(d->file), length);
if(error != 0) {
debug("FileStream::truncate() -- Coundn't truncate the file.");
}

#endif
}

TagLib::uint FileStream::bufferSize()
{
return 1024;
}
为了便于调试,还需要修改taglib\toolkit\tdebuglistener.cpp,以便在调试直接在Output窗口输出调试信息,完整代码如下:
/***************************************************************************
    copyright            : (C) 2013 by Tsuda Kageyu
    email                : tsuda.kageyu@gmail.com
 ***************************************************************************/

/***************************************************************************
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License version   *
 *   2.1 as published by the Free Software Foundation.                     *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful, but   *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
 *   02110-1301  USA                                                       *
 *                                                                         *
 *   Alternatively, this file is available under the Mozilla Public        *
 *   License Version 1.1.  You may obtain a copy of the License at         *
 *   http://www.mozilla.org/MPL/                                           *
 ***************************************************************************/

#include "tdebuglistener.h"

#include 
#include 

#ifdef _WIN32
# include 
#endif

using namespace TagLib;

namespace
{
  class DefaultListener : public DebugListener
  {
  public:
    virtual void printMessage(const String &msg)
    {
#ifdef _WIN32

      const wstring wstr = msg.toWString();
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
	  OutputDebugStringW(wstr.c_str());
#else
      const int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
      if(len != 0) {
        std::vector buf(len);
        WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &buf[0], len, NULL, NULL);

        std::cerr << std::string(&buf[0]);
      }
#endif

#else

      std::cerr << msg;

#endif 
    }
  };

  DefaultListener defaultListener;
}

namespace TagLib
{
  DebugListener *debugListener = &defaultListener;

  DebugListener::DebugListener()
  {
  }

  DebugListener::~DebugListener()
  {
  }

  void setDebugListener(DebugListener *listener)
  {
    if(listener)
      debugListener = listener;
    else
      debugListener = &defaultListener;
  }
}
最后,编译吧,骚年!!!


免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请点击