Wartość uniwersalna

0

Cześć!

Wczytuję pewien plik(XX), który posiada bloki danych które poprzedzone są 4 literową nazwą(Tag) oraz 32bitową liczbą opisującą rozmiar bloku.

Przedstawiam poniżej dość uproszczony fragment:

#include <QDataStream>
#include <QMap>
#include <QFile>

// klasa pomocnicza do wczytania 4-literowego znacznika
class QXXTag: public QByteArray
{
public:
    explicit QXXTag() : QByteArray(4, 0){}
    friend QDataStream& operator>> (QDataStream& stream, QXXTag& tag){
        int size;
        size = stream.readRawData(tag.data(), tag.size());
        if(size != tag.size())
            stream.setStatus(QDataStream::ReadCorruptData);
        return stream;
    }
};

int main(){
    QFile file("test.xx");
    if(!file.open(QFile::ReadOnly)){
        return -1;
    }
    QDataStream stream(&file);

    QMap<QString, QByteArray> map;

    // wczytanie pliku do mapy
    QXXTag tag;
    qint32 size;
    while((stream >> tag >> size).status() == QDataStream::Ok){ // wczytanie 4literowego tag'a oraz rozmiaru
      QByteArray data(size, 0); // ustawienie tablicy na rozmiar `size`
      stream.readRawData(data.data(), data.size()); // wczytanie `size` bajtow do tablicy
      map.insert(tag, data); // wpisanie do mapy w postaci `tag` `data`
    }

    // hmmm

    return 0;
}

Problemem jest teraz interpretacja danych w QByteArray które znajdują się w QMap

    QMap<QString, QByteArray> map;

String w QMap(czyli tag) oznacza w jakiej formie dane mogą być odczytane.
Danymi są tablice(nie zawsze) struktur z polami typu short, int, char[20] itp.
np.

struct xx_tag_kphr{
  char presetName[20]; // po wczytaniu, trzeba będzie przerobić na QByteArray lub QString'a. presetName może nie zawierać \0 na końcu tekstu
  uint16_t preset;
  uint16_t bank;
  uint32_t library;
};

Jeżeli tag ma wartość "kphr" to QByteArray może ważyć N*sizeof(struct xx_tag_kphr).
Gdzie N to ilość takich struktur.

Brakuje mi pomysłu jak to ugryźć, czy przy wczytywaniu pliku od razu parsować dane, zamiast QByteArray zrobić jakiegoś varianta, czy jak?
Porobić templaty na QByteArray? Może klasy które w konstruktorze przyjmują QByteArray?
Fajnie gdyby można było iterować w foreach'u jeśli jest kilka struktur.

Siedzę pół dnia bez nawet nowej literki wpisanej w kodzie i tak dumam, jak to dobrze napisać :(

0

Jeśli chcesz mieć stały typ w mapie to utwórz jakiś interfejs i korzystaj z polimorfizmu dynamicznego. Nie jestem pewien co konkretnie chcesz osiągnąć.

0

Plik który czytam, ma strukturę RIFF, zrobiłem dość przyjemne iterowanie i przeszukiwanie drzewa oraz odwoływanie się w postaci:

QByteArray &data = riffFile["abcd"]["efgh"];

Pola, czy inaczej, budowa pliku nie jest stała, może być więcej pól lub mniej, byle było to co chcę później odczytywać.
Po odwołaniu się do abcd efgh chciałbym mieć możliwość dostępu do pól sktuktury, a jeśli to jest tablica struktur to je przeiterować.

1

Dziedzicznie twoim wrogiem! Zwłaszcza jak dziedziczysz po czymś takim jak QByteArray.
Powinno być tak:

class DataItem
{
public:
    DataItem();

     friend QDataStream& operator>> (QDataStream& stream, DataItem& tag)
    {
         char nameBuff[4];
         stream.readRawData(&nameBuff, sizeof(nameBuff));
         name = QString::fromLatin1(nameBuff, sizeof(nameBuff));
         quint32 dataSize;
         stream >> dataSize;
         data.resize(dataSize);
         stream.readRawData(data.data(), dataSize);
    }

private:
    QString name;
    QByteArray data; 
};

Dodatkowo zapomniałeś o endianie! Wczytując dataSize jest to bardzo ważne. Domyślnie QDataStream używa Big Endian, możliwe, że tym używać LittleEndian.

0
MarekR22 napisał(a):

Dziedzicznie twoim wrogiem! Zwłaszcza jak dziedziczysz po czymś takim jak QByteArray.
Powinno być tak:

class DataItem
{
public:
    DataItem();

     friend QDataStream& operator>> (QDataStream& stream, DataItem& tag)
    {
         char nameBuff[4];
         stream.readRawData(&nameBuff, sizeof(nameBuff));
         name = QString::fromLatin1(nameBuff, sizeof(nameBuff));
         quint32 dataSize;
         stream >> dataSize;
         data.resize(dataSize);
         stream.readRawData(data.data(), dataSize);
    }

private:
    QString name;
    QByteArray data; 
};

Dodatkowo zapomniałeś o endianie! Wczytując dataSize jest to bardzo ważne. Domyślnie QDataStream używa Big Endian, możliwe, że tym używać LittleEndian.

Momencik, dlaczego uważasz że tego typu wczytywanie N znaków jest be?

#include <QDataStream>
#include <QDebug>
#include <QByteArray>

int main(){
    // przygotowanie
    char msg[] = "12345678";
    QByteArray qtest(msg, sizeof(msg));
    QDataStream stream(qtest);

    // odczytanie N znakow ze strumienia
    const int N = 4;

    QByteArray ba(N, 0);
    stream.readRawData(ba.data(), ba.size());

    qDebug() << ba;

    return 0;
}

Przy Twoim kodzie, nawet nie sprawdzacz czy faktycznie odczytałeś tyle znaków ile ma być.
Nie mogę odczytywać wielkości zaraz za Tag'iem, ponieważ zależy co to za Tag. Tak pliki RIFF w niektórych przypadkach mają (np. tag INFO) :)

Jeśli chodzi o endiana, ustawiłem sobie na początku, po otwarciu pliku, jeszcze myślę czy za każdym razem ustawiać gdzie korzystam, czy nie(QDataStream po klasie jest "rozsyłany" jako refenecja)

Powracając do problemu, naskrobałem coś takiego:


template<int S>
class QSf2Char: public QByteArray
{
public:
    QSf2Char() : QByteArray(S, 0){}
    friend QDataStream& operator>> (QDataStream& stream, QSf2Char<S>& chars){
        int size;
        size = stream.readRawData(chars.data(), chars.size());
        if(size != chars.size())
            stream.setStatus(QDataStream::ReadCorruptData);
        return stream;
    }
};

struct QSf2PHDR
{
public:
    friend QDataStream& operator>> (QDataStream& stream, QSf2PHDR& phdr){
        stream  >> phdr.presetName >> phdr.preset
                >> phdr.bank >> phdr.presetBagNdx
                >> phdr.library >> phdr.genre >> phdr.morphology;
        return stream;
    }

    QSf2Char<20> presetName;
    quint16      preset;
    quint16      bank;
    quint16      presetBagNdx;
    quint32      library;
    quint32      genre;
    quint32      morphology;
};

template<class T>
class QSf2List: public QList<T>
{
public:
    QSf2List():QList<T>(){}
    QSf2List(const QByteArray& data):QSf2List(){
        T t;
        QDataStream stream(data);
        while(_(stream >> t))
            this->append(t);
    }
};

Niezbyt podoba mi się klepanie takich klas jak QSf2PHDR, ale wykorzystanie wygląda tak:

    QSf2List<QSf2PHDR> phdr = sf2["pdta"]["phdr"].data(); // metoda data() zwraca QByteArray
    foreach(QSf2PHDR it, phdr){
        qDebug() << "Name: " << it.presetName;
    }
0

Kilka sprostowań też z mojej strony.
Temat założyłem dość mocno okrajając założenia itp, a chciałem się skupić jak wygodnie konwertować w locie dane które już są odczytane z pliku.
Wczytując plik, nie jestem w stanie określić jak niektóre dane parsować, bo jest to uniwersalny odczyt plików RIFF.
Później, czy to będzie wav, czy sf2 to "zdekodują" kolejne klasy które będą chciały coś z tym zrobić.

@MarekR22 zauważ coś takiego:

#include <QDataStream>
#include <QDebug>
#include <QByteArray>

int main(){
    // przygotowanie
    char msg[] = "1";
    QByteArray qtest(msg, sizeof(msg));
    QDataStream stream(qtest);

    // odczytanie N znakow ze strumienia
    const int N = 4;

    QByteArray ba(N, 0);
    stream.readRawData(ba.data(), ba.size());
    qDebug() << "Udalo sie? " << (stream.status() == QDataStream::Ok); // surprise! true...
    qDebug() << ba;

    return 0;
}

Nie wywali błędu, a tutaj krytycznie potrzebuję informację czy po odczytaniu N bajtów jest wszystko ok, dlatego sprawdzałem dodatkowo czy została odczytana taka ilość danych jaka powinna:
I teraz mogę np. zrobić coś takiego:

#include <QDataStream>
#include <QDebug>
#include <QByteArray>

// ponizsza funkcje poruszalem juz na forum, mi sie bardzo sprawdza
inline
bool _(const QDataStream& stream){
    return stream.status() == QDataStream::Ok;
}


template<int S>
class QSf2Char: public QByteArray
{
public:
    QSf2Char() : QByteArray(S, 0){}
    friend QDataStream& operator>> (QDataStream& stream, QSf2Char<S>& chars){
        int size;
        size = stream.readRawData(chars.data(), chars.size());
        if(size != chars.size()) stream.setStatus(QDataStream::ReadCorruptData); // sprobuj zakomentowac :) <################
        return stream;
    }
};

int main(){
    // przygotowanie
    char msg[] = "0123456789ABCDEF";
    QByteArray qtest(msg, sizeof(msg));
    QDataStream stream(qtest);

    // odczytanie N znakow ze strumienia
    const int N = 4;

    QSf2Char<N> str;
    while(_(stream >> str))
        qDebug() << str;

    return 0;
}

Wynik:

"0123" 
"4567" 
"89AB" 
"CDEF" 
1

Trzeba było zacząć od tego, że jest to RIFF, zaoszczędziłbyś dużo czasu, bo format jest dobrze udokumentowany.

const int RiffIDSize = 4;

class RiffBlock
{
public:
     RiffBlock(const QByteArray& _blockID, const QByteArray& _data)
     : blockID(_blockID)
     , data(_data)
     {}

private;
     QByteArray blockID;
     QByteArray data;
};

class RiffData
{
public:

    bool LoadFrom(QIODevice *dev)
    {
          blocks.clear();
          QDataStream stream(dev, QIODevice::ReadOnly);
          stream.setByteOrder(QDataStream::LittleEndian);

          if (!ReadHeader(stream))
               return false;

          int blocksSizeRead = 0;
          while(!stream.atEnd() && blocksSizeRead<totalSize)
          {
               int result = ReadBloc(stream);
               if (result <0)
                    return false;

               blocksSizeRead += result;
          }
          return blocksSizeRead == totalSize;
    }

    void BlockHasBeenRead(const QByteArray& blockID, const QByteArray& data)
    {
          blocks.append(RiffBlock(blockID, data));
    }

priavate:
    void ReadHeader(QDataStream& stream)
    {
          char buff[RiffIDSize];
          int result = stream.readRawData(buff, RiffIDSize);
          if (result != RiffIDSize)
          {
                stream.setStatus(QDataStream::ReadCorruptData);
                return false;
          }
          if (memcmp(buff, "RIFF", RiffIDSize) != 0)
          {
                stream.setStatus(QDataStream::ReadCorruptData);
                return false;
          }
          stream >> totalSize;
          result = stream.readRawData(buff, RiffIDSize);
          if (result != RiffIDSize)
          {
                stream.setStatus(QDataStream::ReadCorruptData);
                return false;
          }
          format = QByteArray::fromRawData(buff, RiffIDSize);

          return stream.status() == QDataStream::Ok;
    }

    int ReadBlock(QDataStream& stream)
    {
          QByteArrray blockID(RiffIDSize, 0);
          int result = stream.readRawData(blockID.data(), RiffIDSize);
          if (result != RiffIDSize)
               return -1;

          uint32 dataSize;
          stream >> dataSize;
          result += 4 + dataSize;
          QByteArrray data(dataSize, 0);
          if (stream.readRawData(data.data(), dataSize) != dataSize)
          {
                stream.setStatus(QDataStream::ReadCorruptData);
                return -1;
          }
          BlockHasBeenRead(blockID, data);
          return result;
    }

private:
    uint32 totalSize;
    QByteArray format;
    QVector<RiffBlock> blocks;
};
0

Widzisz @MarekR22, nie chciałem pisać zbyt dużo bo nie plików RIFF dotyczy problem. Im więcej tekstu tym mniej się chce czytać i widzę że to się sprawdza :)

Zanim jeszcze odpowiedziałeś, żeby zaspokoić ciekawość napisałem

Otwornica napisał(a):

Plik który czytam, ma strukturę RIFF, zrobiłem dość przyjemne iterowanie i przeszukiwanie drzewa oraz odwoływanie się w postaci:

QByteArray &data = riffFile["abcd"]["efgh"];

Wracając do problemu

Jak zauważysz, dane które wczytałeś swoim kodem(podobnie jak u mnie), trzymasz w klasie RiffBlock a dokładniej w:

QByteArray data;

Zacytuję wymieniony problem z pierwszego postu:

Otwornica napisał(a):

Problemem jest teraz interpretacja danych w QByteArray

Problem ten rozwiązałem w poście 5 licząc posty w temacie od góry.
Czyli porobiłem klasy (np. QSf2PHDR), które w konstruktorze przyjmują QByteArray i odpowiednio je parsują.
Dodatkowo, klasa QSf2List potrafi zrobić listę powyższych klas, jeśli danych jest więcej.

Co do kodu @MarekR22, kod ten nie czyta pliku RIFF w postaci drzewa - ale to nie jest problem, bo taką klasę już dawno napisałem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1